[Sqlalchemy-commits] [1316] sqlalchemy/branches/schema/test: dev
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-22 17:36:49
|
<!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>[1316] sqlalchemy/branches/schema/test: dev</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1316</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-22 12:36:30 -0500 (Sat, 22 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>dev</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyattributespy">sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginestrategiespy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingmapperpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingpropertiespy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingsyncpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/sync.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingunitofworkpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py</a></li> <li><a href="#sqlalchemybranchesschemalibsqlalchemymappingutilpy">sqlalchemy/branches/schema/lib/sqlalchemy/mapping/util.py</a></li> <li><a href="#sqlalchemybranchesschematestobjectstorepy">sqlalchemy/branches/schema/test/objectstore.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginethreadlocalpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/threadlocal.py</a></li> </ul> <h3>Removed Paths</h3> <ul> <li><a href="#sqlalchemybranchesschemalibsqlalchemyenginetransactionalpy">sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemybranchesschemalibsqlalchemyattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/attributes.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -353,12 +353,10 @@ </span><span class="cx"> def set_attribute(self, obj, key, value, **kwargs): </span><span class="cx"> """sets the value of an object's attribute.""" </span><span class="cx"> self.get_unexec_history(obj, key).setattr(value, **kwargs) </span><del>- self.value_changed(obj, key, value) </del><span class="cx"> </span><span class="cx"> def delete_attribute(self, obj, key, **kwargs): </span><span class="cx"> """deletes the value from an object's attribute.""" </span><span class="cx"> self.get_unexec_history(obj, key).delattr(**kwargs) </span><del>- self.value_changed(obj, key, None) </del><span class="cx"> </span><span class="cx"> def rollback(self, *obj): </span><span class="cx"> """rolls back all attribute changes on the given list of objects, </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginestrategiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/strategies.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -7,7 +7,7 @@ </span><span class="cx"> import re </span><span class="cx"> import cgi </span><span class="cx"> </span><del>-from sqlalchemy.engine import base, default, transactional </del><ins>+from sqlalchemy.engine import base, default, threadlocal </ins><span class="cx"> </span><span class="cx"> strategies = {} </span><span class="cx"> </span><span class="lines">@@ -57,9 +57,9 @@ </span><span class="cx"> if poolclass is not None: </span><span class="cx"> poolargs.setdefault('poolclass', poolclass) </span><span class="cx"> poolargs['use_threadlocal'] = True </span><del>- provider = transactional.TLocalConnectionProvider(dialect, opts, **poolargs) </del><ins>+ provider = threadlocal.TLocalConnectionProvider(dialect, opts, **poolargs) </ins><span class="cx"> </span><del>- return transactional.TLEngine(provider, dialect, **kwargs) </del><ins>+ return threadlocal.TLEngine(provider, dialect, **kwargs) </ins><span class="cx"> ThreadLocalEngineStrategy() </span><span class="cx"> </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginethreadlocalpyfromrev1315sqlalchemybranchesschemalibsqlalchemyenginetransactionalpy"></a> <div class="copfile"><h4>Copied: sqlalchemy/branches/schema/lib/sqlalchemy/engine/threadlocal.py (from rev 1315, sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py) (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/threadlocal.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -0,0 +1,84 @@ </span><ins>+from sqlalchemy import schema, exceptions, util, sql, types +import StringIO, sys, re +import base, default + +"""provides a thread-local transactional wrapper around the basic ComposedSQLEngine. multiple calls to engine.connect() +will return the same connection for the same thread. also provides begin/commit methods on the engine itself +which correspond to a thread-local transaction.""" + +class TLTransaction(base.Transaction): + def rollback(self): + try: + base.Transaction.rollback(self) + finally: + try: + del self.connection.engine.context.transaction + except AttributeError: + pass + def commit(self): + try: + base.Transaction.commit(self) + stack = self.connection.engine.context.transaction + stack.pop() + if len(stack) == 0: + del self.connection.engine.context.transaction + except: + try: + del self.connection.engine.context.transaction + except AttributeError: + pass + raise + +class TLConnection(base.Connection): + def _create_transaction(self, parent): + return TLTransaction(self, parent) + def begin(self): + t = base.Connection.begin(self) + if not hasattr(self.engine.context, 'transaction'): + self.engine.context.transaction = [] + self.engine.context.transaction.append(t) + return t + +class TLEngine(base.ComposedSQLEngine): + """a ComposedSQLEngine that includes support for thread-local managed transactions. This engine + is better suited to be used with threadlocal Pool object.""" + def __init__(self, *args, **kwargs): + """the TLEngine relies upon the ConnectionProvider having "threadlocal" behavior, + so that once a connection is checked out for the current thread, you get that same connection + repeatedly.""" + base.ComposedSQLEngine.__init__(self, *args, **kwargs) + self.context = util.ThreadLocal() + def raw_connection(self): + """returns a DBAPI connection.""" + return self.connection_provider.get_connection() + def connect(self, **kwargs): + """returns a Connection that is not thread-locally scoped. this is the equilvalent to calling + "connect()" on a ComposedSQLEngine.""" + return base.Connection(self, self.connection_provider.unique_connection()) + def contextual_connect(self, **kwargs): + """returns a TLConnection which is thread-locally scoped.""" + return TLConnection(self, **kwargs) + def begin(self): + return self.connect().begin() + def commit(self): + if hasattr(self.context, 'transaction'): + self.context.transaction[-1].commit() + def rollback(self): + if hasattr(self.context, 'transaction'): + self.context.transaction[-1].rollback() + def transaction(self, func, *args, **kwargs): + """executes the given function within a transaction boundary. this is a shortcut for + explicitly calling begin() and commit() and optionally rollback() when execptions are raised. + The given *args and **kwargs will be passed to the function as well, which could be handy + in constructing decorators.""" + trans = self.begin() + try: + func(*args, **kwargs) + except: + trans.rollback() + raise + trans.commit() + +class TLocalConnectionProvider(default.PoolConnectionProvider): + def unique_connection(self): + return self._pool.unique_connection() </ins></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemyenginetransactionalpy"></a> <div class="delfile"><h4>Deleted: sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/engine/transactional.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -1,84 +0,0 @@ </span><del>-from sqlalchemy import schema, exceptions, util, sql, types -import StringIO, sys, re -import base, default - -"""provides a thread-local transactional wrapper around the basic ComposedSQLEngine. multiple calls to engine.connect() -will return the same connection for the same thread. also provides begin/commit methods on the engine itself -which correspond to a thread-local transaction.""" - -class TLTransaction(base.Transaction): - def rollback(self): - try: - base.Transaction.rollback(self) - finally: - try: - del self.connection.engine.context.transaction - except AttributeError: - pass - def commit(self): - try: - base.Transaction.commit(self) - stack = self.connection.engine.context.transaction - stack.pop() - if len(stack) == 0: - del self.connection.engine.context.transaction - except: - try: - del self.connection.engine.context.transaction - except AttributeError: - pass - raise - -class TLConnection(base.Connection): - def _create_transaction(self, parent): - return TLTransaction(self, parent) - def begin(self): - t = base.Connection.begin(self) - if not hasattr(self.engine.context, 'transaction'): - self.engine.context.transaction = [] - self.engine.context.transaction.append(t) - return t - -class TLEngine(base.ComposedSQLEngine): - """a ComposedSQLEngine that includes support for thread-local managed transactions. This engine - is better suited to be used with threadlocal Pool object.""" - def __init__(self, *args, **kwargs): - """the TLEngine relies upon the ConnectionProvider having "threadlocal" behavior, - so that once a connection is checked out for the current thread, you get that same connection - repeatedly.""" - base.ComposedSQLEngine.__init__(self, *args, **kwargs) - self.context = util.ThreadLocal() - def raw_connection(self): - """returns a DBAPI connection.""" - return self.connection_provider.unique_connection() - def connect(self, **kwargs): - """returns a Connection that is not thread-locally scoped. this is the equilvalent to calling - "connect()" on a ComposedSQLEngine.""" - return base.Connection(self, self.connection_provider.unique_connection()) - def contextual_connect(self, **kwargs): - """returns a TLConnection which is thread-locally scoped.""" - return TLConnection(self, **kwargs) - def begin(self): - return self.connect().begin() - def commit(self): - if hasattr(self.context, 'transaction'): - self.context.transaction[-1].commit() - def rollback(self): - if hasattr(self.context, 'transaction'): - self.context.transaction[-1].rollback() - def transaction(self, func, *args, **kwargs): - """executes the given function within a transaction boundary. this is a shortcut for - explicitly calling begin() and commit() and optionally rollback() when execptions are raised. - The given *args and **kwargs will be passed to the function as well, which could be handy - in constructing decorators.""" - trans = self.begin() - try: - func(*args, **kwargs) - except: - trans.rollback() - raise - trans.commit() - -class TLocalConnectionProvider(default.PoolConnectionProvider): - def unique_connection(self): - return self._pool.unique_connection() </del></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/mapper.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -15,6 +15,7 @@ </span><span class="cx"> import objectstore </span><span class="cx"> import sys </span><span class="cx"> import weakref </span><ins>+import sets </ins><span class="cx"> </span><span class="cx"> # a dictionary mapping classes to their primary mappers </span><span class="cx"> mapper_registry = weakref.WeakKeyDictionary() </span><span class="lines">@@ -690,17 +691,15 @@ </span><span class="cx"> if self.inherits is not None: </span><span class="cx"> uowcommit.register_dependency(self.inherits, self) </span><span class="cx"> </span><del>- def cascade_iterator(self, type, object): </del><ins>+ def cascade_iterator(self, type, object, recursive=None): + if recursive is None: + recursive=sets.Set() + recursive.add(object) </ins><span class="cx"> yield object </span><span class="cx"> for prop in self.props.values(): </span><del>- for c in prop.cascade_iterator(type, object): </del><ins>+ for c in prop.cascade_iterator(type, object, recursive): </ins><span class="cx"> yield c </span><span class="cx"> </span><del>-# def register_deleted(self, obj, uow): -# for prop in self.props.values(): -# prop.register_deleted(obj, uow) - - </del><span class="cx"> def _identity_key(self, row): </span><span class="cx"> return objectstore.get_row_key(row, self.class_, self.pks_by_table[self.table], self.entity_name) </span><span class="cx"> </span><span class="lines">@@ -787,7 +786,7 @@ </span><span class="cx"> """called when the mapper receives a row. instance is the parent instance </span><span class="cx"> corresponding to the row. """ </span><span class="cx"> raise NotImplementedError() </span><del>- def cascade_iterator(self, type, object): </del><ins>+ def cascade_iterator(self, type, object, recursive=None): </ins><span class="cx"> return [] </span><span class="cx"> def copy(self): </span><span class="cx"> raise NotImplementedError() </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/objectstore.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -28,7 +28,7 @@ </span><span class="cx"> def add(self, connection_or_engine): </span><span class="cx"> if self.connections.has_key(connection_or_engine): </span><span class="cx"> return self.connections[connection_or_engine][0] </span><del>- c = connection_or_engine.connect() </del><ins>+ c = connection_or_engine.contextual_connect() </ins><span class="cx"> e = c.engine </span><span class="cx"> if not self.connections.has_key(e): </span><span class="cx"> self.connections[e] = (c, c.begin()) </span><span class="lines">@@ -91,20 +91,21 @@ </span><span class="cx"> method which performs select operations for Mapper and Query. </span><span class="cx"> if this Session is transactional, </span><span class="cx"> the connection will be in the context of this session's transaction. otherwise, the connection </span><del>- will be unique. </del><ins>+ is returned by the contextual_connect method, which some Engines override to return a thread-local + connection, and will have close_with_result set to True. </ins><span class="cx"> </span><del>- the given **kwargs will be sent to the engine's connect() method, if no transaction is in progress.""" </del><ins>+ the given **kwargs will be sent to the engine's contextual_connect() method, if no transaction is in progress.""" </ins><span class="cx"> if self.transaction is not None: </span><span class="cx"> return self.transaction.connection(mapper) </span><span class="cx"> else: </span><del>- return self.connect(mapper, **kwargs) </del><ins>+ return self.get_bind(mapper).contextual_connect(**kwargs) </ins><span class="cx"> def execute(self, mapper, clause, params, **kwargs): </span><span class="cx"> """using the given mapper to identify the appropriate Engine or Connection to be used for statement execution, </span><span class="cx"> executes the given ClauseElement using the provided parameter dictionary. Returns a ResultProxy corresponding </span><span class="cx"> to the execution's results. If this method allocates a new Connection for the operation, then the ResultProxy's close() </span><span class="cx"> method will release the resources of the underlying Connection, otherwise its a no-op. </span><span class="cx"> """ </span><del>- return self.connection(mapper, close_with_result=True).execute(clause, params, **kwargs) </del><ins>+ return self.connection(mapper).execute(clause, params, **kwargs) </ins><span class="cx"> def close(self): </span><span class="cx"> """closes this Session. </span><span class="cx"> </span><span class="lines">@@ -207,7 +208,11 @@ </span><span class="cx"> """flushes all the object modifications present in this session to the database. if object </span><span class="cx"> arguments are given, then only those objects (and immediate dependencies) are flushed.""" </span><span class="cx"> self.uow.flush(self, *obj) </span><del>- </del><ins>+ + def load(self, class_, *ident): + """given a class and a primary key identifier, loads the corresponding object.""" + return self.query(class_).get(*ident) + </ins><span class="cx"> def refresh(self, *obj): </span><span class="cx"> """reloads the attributes for the given objects from the database, clears </span><span class="cx"> any changes made.""" </span><span class="lines">@@ -267,7 +272,7 @@ </span><span class="cx"> self._register_new(object) </span><span class="cx"> </span><span class="cx"> def _update_impl(self, object, **kwargs): </span><del>- if self._is_bound(object): </del><ins>+ if self._is_bound(object) and object not in self.deleted: </ins><span class="cx"> return </span><span class="cx"> if not hasattr(object, '_instance_key'): </span><span class="cx"> raise InvalidRequestError("Instance '%s' is not persisted" % repr(object)) </span><span class="lines">@@ -291,7 +296,18 @@ </span><span class="cx"> def _bind_to(self, obj): </span><span class="cx"> """given an object, binds it to this session. changes on the object will affect </span><span class="cx"> the currently scoped UnitOfWork maintained by this session.""" </span><del>- obj._sa_session_id = self.hash_key </del><ins>+ if getattr(obj, '_sa_session_id', None) != self.hash_key: + old = getattr(obj, '_sa_session_id', None) + # remove from old session. we do this gingerly since _sessions is a WeakValueDict + # and it might be affected by other threads + if old is not None: + try: + sess = _sessions[old] + except KeyError: + sess = None + if sess is not None: + sess.expunge(old) + obj._sa_session_id = self.hash_key </ins><span class="cx"> def _is_bound(self, obj): </span><span class="cx"> return getattr(obj, '_sa_session_id', None) == self.hash_key </span><span class="cx"> def __contains__(self, obj): </span><span class="lines">@@ -307,7 +323,6 @@ </span><span class="cx"> dirty = property(lambda s:s.uow.dirty) </span><span class="cx"> deleted = property(lambda s:s.uow.deleted) </span><span class="cx"> new = property(lambda s:s.uow.new) </span><del>- modified_lists = property(lambda s:s.uow.modified_lists) </del><span class="cx"> identity_map = property(lambda s:s.uow.identity_map) </span><span class="cx"> </span><span class="cx"> def clear(self): </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/properties.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -17,6 +17,7 @@ </span><span class="cx"> import sync </span><span class="cx"> import mapper </span><span class="cx"> import objectstore </span><ins>+import util as mapperutil </ins><span class="cx"> from sqlalchemy.exceptions import * </span><span class="cx"> import sets </span><span class="cx"> </span><span class="lines">@@ -138,15 +139,13 @@ </span><span class="cx"> else: </span><span class="cx"> self.foreigntable = None </span><span class="cx"> </span><del>- # hibernate cascades: - # create, merge, save-update, delete, lock, refresh, evict, replicate. </del><span class="cx"> if cascade is not None: </span><del>- self.cascade = sets.Set([c.strip() for c in cascade.split(',')]) </del><ins>+ self.cascade = mapperutil.CascadeOptions(cascade) </ins><span class="cx"> else: </span><span class="cx"> if private: </span><del>- self.cascade = sets.Set(["delete-orphan", "delete"]) </del><ins>+ self.cascade = mapperutil.CascadeOptions("save-update, delete-orphan, delete") </ins><span class="cx"> else: </span><del>- self.cascade = sets.Set() </del><ins>+ self.cascade = mapperutil.CascadeOptions("save-update") </ins><span class="cx"> </span><span class="cx"> self.association = association </span><span class="cx"> if selectalias is not None: </span><span class="lines">@@ -161,22 +160,27 @@ </span><span class="cx"> self.backref = backref </span><span class="cx"> self.is_backref = is_backref </span><span class="cx"> </span><del>- private = property(lambda s:"delete-orphan" in s.cascade) </del><ins>+ private = property(lambda s:s.cascade.delete_orphan) </ins><span class="cx"> </span><del>- def cascade_iterator(self, type, object): </del><ins>+ def cascade_iterator(self, type, object, recursive=None): </ins><span class="cx"> </span><span class="cx"> if not type in self.cascade: </span><span class="cx"> return </span><span class="cx"> </span><ins>+ if recursive is None: + recursive = sets.Set() + </ins><span class="cx"> if self.uselist: </span><span class="cx"> childlist = objectstore.global_attributes.get_history(object, self.key, passive = False) </span><span class="cx"> else: </span><span class="cx"> childlist = objectstore.global_attributes.get_history(object, self.key) </span><span class="cx"> for c in childlist.added_items() + childlist.deleted_items() + childlist.unchanged_items(): </span><del>- if c is not None: </del><ins>+ if c is not None and c not in recursive: + recursive.add(c) </ins><span class="cx"> yield c </span><del>- for c2 in self.mapper.cascade_iterator(type, c): - yield c2 </del><ins>+ for c2 in self.mapper.cascade_iterator(type, c, recursive): + if c2 not in recursive: + yield c2 </ins><span class="cx"> </span><span class="cx"> def copy(self): </span><span class="cx"> x = self.__class__.__new__(self.__class__) </span><span class="lines">@@ -330,6 +334,7 @@ </span><span class="cx"> return None </span><span class="cx"> </span><span class="cx"> </span><ins>+ # TODO: this should be moved to an external object </ins><span class="cx"> class MapperStub(object): </span><span class="cx"> """poses as a Mapper representing the association table in a many-to-many </span><span class="cx"> join, when performing a commit(). </span><span class="lines">@@ -345,7 +350,8 @@ </span><span class="cx"> pass </span><span class="cx"> def _primary_mapper(self): </span><span class="cx"> return self </span><del>- </del><ins>+ + # TODO: this method should be moved to an external object </ins><span class="cx"> def register_dependencies(self, uowcommit): </span><span class="cx"> """tells a UOWTransaction what mappers are dependent on which, with regards </span><span class="cx"> to the two or three mappers handled by this PropertyLoader. </span><span class="lines">@@ -409,9 +415,11 @@ </span><span class="cx"> else: </span><span class="cx"> raise AssertionError(" no foreign key ?") </span><span class="cx"> </span><ins>+ # TODO: this method should be moved to an external object </ins><span class="cx"> def get_object_dependencies(self, obj, uowcommit, passive = True): </span><span class="cx"> return uowcommit.uow.attributes.get_history(obj, self.key, passive = passive) </span><span class="cx"> </span><ins>+ # TODO: this method should be moved to an external object </ins><span class="cx"> def whose_dependent_on_who(self, obj1, obj2): </span><span class="cx"> """given an object pair assuming obj2 is a child of obj1, returns a tuple </span><span class="cx"> with the dependent object second, or None if they are equal. </span><span class="lines">@@ -424,6 +432,7 @@ </span><span class="cx"> else: </span><span class="cx"> return (obj2, obj1) </span><span class="cx"> </span><ins>+ # TODO: this method should be moved to an external object </ins><span class="cx"> def process_dependencies(self, task, deplist, uowcommit, delete = False): </span><span class="cx"> """this method is called during a commit operation to synchronize data between a parent and child object. </span><span class="cx"> it also can establish child or parent objects within the unit of work as "to be saved" or "deleted" </span><span class="lines">@@ -431,9 +440,7 @@ </span><span class="cx"> #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) </span><span class="cx"> </span><span class="cx"> def getlist(obj, passive=True): </span><del>- l = self.get_object_dependencies(obj, uowcommit, passive) - uowcommit.register_saved_history(l) - return l </del><ins>+ return self.get_object_dependencies(obj, uowcommit, passive) </ins><span class="cx"> </span><span class="cx"> connection = uowcommit.transaction.connection(self.mapper) </span><span class="cx"> </span><span class="lines">@@ -564,6 +571,7 @@ </span><span class="cx"> #print "PLAIN PROPLOADER EXEC NON-PRIAMRY", repr(id(self)), repr(self.mapper.class_), self.key </span><span class="cx"> objectstore.global_attributes.create_history(instance, self.key, self.uselist, cascade=self.cascade) </span><span class="cx"> </span><ins>+ # TODO: this method should be moved to an external object </ins><span class="cx"> def _compile_synchronizers(self): </span><span class="cx"> """assembles a list of 'synchronization rules', which are instructions on how to populate </span><span class="cx"> the objects on each side of a relationship. This is done when a PropertyLoader is </span><span class="lines">@@ -584,6 +592,7 @@ </span><span class="cx"> else: </span><span class="cx"> self.syncrules.compile(self.primaryjoin, parent_tables, target_tables) </span><span class="cx"> </span><ins>+ # TODO: this method should be moved to an external object </ins><span class="cx"> def _synchronize(self, obj, child, associationrow, clearkeys): </span><span class="cx"> """called during a commit to execute the full list of syncrules on the </span><span class="cx"> given object/child/optional association row""" </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingsyncpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/sync.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/sync.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/sync.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -10,7 +10,7 @@ </span><span class="cx"> import sqlalchemy.schema as schema </span><span class="cx"> from sqlalchemy.exceptions import * </span><span class="cx"> </span><del>-"""contains the ClauseSynchronizer class which is used to map attributes between two objects </del><ins>+"""contains the ClauseSynchronizer class, which is used to map attributes between two objects </ins><span class="cx"> in a manner corresponding to a SQL clause that compares column values.""" </span><span class="cx"> </span><span class="cx"> ONETOMANY = 0 </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingunitofworkpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/unitofwork.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -46,13 +46,13 @@ </span><span class="cx"> sess = get_session(obj, raiseerror=False) </span><span class="cx"> if sess is None: </span><span class="cx"> return </span><del>- sess.modified_lists.append(self) </del><ins>+ sess._register_dirty(obj) </ins><span class="cx"> if self.cascade is not None: </span><span class="cx"> if isdelete: </span><del>- if "delete-orphan" in self.cascade: </del><ins>+ if self.cascade.delete_orphan: </ins><span class="cx"> sess.delete(item) </span><span class="cx"> else: </span><del>- if "save-update" in self.cascade: </del><ins>+ if self.cascade.save_update: </ins><span class="cx"> sess.save_or_update(item) </span><span class="cx"> def append(self, item, _mapper_nohistory = False): </span><span class="cx"> if _mapper_nohistory: </span><span class="lines">@@ -68,13 +68,13 @@ </span><span class="cx"> obj = self.obj </span><span class="cx"> sess = get_session(obj, raiseerror=False) </span><span class="cx"> if sess is not None: </span><del>- sess.save_or_update(obj) </del><ins>+ sess._register_dirty(obj) </ins><span class="cx"> if self.cascade is not None: </span><del>- if "delete-orphan" in self.cascade: </del><ins>+ if oldvalue is not None and self.cascade.delete_orphan: </ins><span class="cx"> sess.delete(oldvalue) </span><del>- if "save-update" in self.cascade: </del><ins>+ if newvalue is not None and self.cascade.save_update: </ins><span class="cx"> sess.save_or_update(newvalue) </span><del>- </del><ins>+ </ins><span class="cx"> class UOWAttributeManager(attributes.AttributeManager): </span><span class="cx"> """overrides AttributeManager to provide unit-of-work "dirty" hooks when scalar attribues are modified, plus factory methods for UOWProperrty/UOWListElement.""" </span><span class="cx"> def __init__(self): </span><span class="lines">@@ -90,7 +90,7 @@ </span><span class="cx"> return UOWListElement(obj, key, list_, **kwargs) </span><span class="cx"> </span><span class="cx"> class UnitOfWork(object): </span><del>- """main UOW object which stores lists of dirty/new/deleted objects, as well as 'modified_lists' for list attributes. provides top-level "flush" functionality as well as the transaction boundaries with the SQLEngine(s) involved in a write operation.""" </del><ins>+ """main UOW object which stores lists of dirty/new/deleted objects. provides top-level "flush" functionality as well as the transaction boundaries with the SQLEngine(s) involved in a write operation.""" </ins><span class="cx"> def __init__(self, identity_map=None): </span><span class="cx"> if identity_map is not None: </span><span class="cx"> self.identity_map = identity_map </span><span class="lines">@@ -101,9 +101,6 @@ </span><span class="cx"> self.new = util.HashSet(ordered = True) </span><span class="cx"> self.dirty = util.HashSet() </span><span class="cx"> </span><del>- # guess what. we dont even need this anymore ! hooray - TODO: take it out - self.modified_lists = util.HashSet() - </del><span class="cx"> self.deleted = util.HashSet() </span><span class="cx"> </span><span class="cx"> def get(self, class_, *id): </span><span class="lines">@@ -198,12 +195,14 @@ </span><span class="cx"> raise InvalidRequestError("Object '%s' already has an identity - it cant be registered as new" % repr(obj)) </span><span class="cx"> if not self.new.contains(obj): </span><span class="cx"> self.new.append(obj) </span><ins>+ self.unregister_deleted(obj) </ins><span class="cx"> </span><span class="cx"> def register_dirty(self, obj): </span><span class="cx"> if not self.dirty.contains(obj): </span><span class="cx"> self._validate_obj(obj) </span><span class="cx"> self.dirty.append(obj) </span><del>- </del><ins>+ self.unregister_deleted(obj) + </ins><span class="cx"> def is_dirty(self, obj): </span><span class="cx"> if not self.dirty.contains(obj): </span><span class="cx"> return False </span><span class="lines">@@ -236,15 +235,6 @@ </span><span class="cx"> continue </span><span class="cx"> flush_context.register_object(obj) </span><span class="cx"> </span><del>- for item in self.modified_lists: - obj = item.obj - if objset is not None and not objset.contains(obj): - continue - if self.deleted.contains(obj): - continue - flush_context.register_object(obj, listonly = True) - flush_context.register_saved_history(item) - </del><span class="cx"> for obj in self.deleted: </span><span class="cx"> if objset is not None and not objset.contains(obj): </span><span class="cx"> continue </span><span class="lines">@@ -285,7 +275,6 @@ </span><span class="cx"> self.mappers = util.HashSet() </span><span class="cx"> self.dependencies = {} </span><span class="cx"> self.tasks = {} </span><del>- self.saved_histories = util.HashSet() </del><span class="cx"> self.__modified = False </span><span class="cx"> </span><span class="cx"> def register_object(self, obj, isdelete = False, listonly = False, postupdate=False, **kwargs): </span><span class="lines">@@ -362,9 +351,6 @@ </span><span class="cx"> task.dependencies.append(UOWDependencyProcessor(processor, targettask, isdeletefrom)) </span><span class="cx"> self.__modified = True </span><span class="cx"> </span><del>- def register_saved_history(self, listobj): - self.saved_histories.append(listobj) - </del><span class="cx"> def execute(self, echo=False): </span><span class="cx"> for task in self.tasks.values(): </span><span class="cx"> task.mapper.register_dependencies(self) </span><span class="lines">@@ -394,19 +380,7 @@ </span><span class="cx"> self.uow._remove_deleted(elem.obj) </span><span class="cx"> else: </span><span class="cx"> self.uow.register_clean(elem.obj) </span><del>- - for obj in self.saved_histories: - try: - obj.commit() - del self.uow.modified_lists[obj] - except KeyError: - pass </del><span class="cx"> </span><del>- # this assertion only applies to a full flush(), not a - # partial one - #if len(self.uow.new) > 0 or len(self.uow.dirty) >0 or len(self.uow.modified_lists) > 0: - # raise "assertion failed" - </del><span class="cx"> def _sort_dependencies(self): </span><span class="cx"> """creates a hierarchical tree of dependent tasks. the root node is returned. </span><span class="cx"> when the root node is executed, it also executes its child tasks recursively.""" </span><span class="lines">@@ -668,12 +642,7 @@ </span><span class="cx"> if not o in childtask.objects: </span><span class="cx"> # item needs to be saved since its added, or attached to a deleted object </span><span class="cx"> if isdelete: </span><del>- childtask.append(o, "delete" in processor.cascade) - #if isdelete and "delete" in processor.cascade: - # childstask.append(o, True) - #elif not isdelete and "save-update" in processor.cascade: - # childtask.append(o, False) - #childtask.append(o, isdelete=isdelete and dep.processor.private) </del><ins>+ childtask.append(o, processor.cascade.delete) </ins><span class="cx"> if cyclicaldep: </span><span class="cx"> # cyclical, so create a placeholder UOWTask that may be built into the </span><span class="cx"> # final task tree </span></span></pre></div> <a id="sqlalchemybranchesschemalibsqlalchemymappingutilpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/lib/sqlalchemy/mapping/util.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/lib/sqlalchemy/mapping/util.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/lib/sqlalchemy/mapping/util.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -6,6 +6,7 @@ </span><span class="cx"> </span><span class="cx"> </span><span class="cx"> import sqlalchemy.sql as sql </span><ins>+import sets </ins><span class="cx"> </span><span class="cx"> class TableFinder(sql.ClauseVisitor): </span><span class="cx"> """given a Clause, locates all the Tables within it into a list.""" </span><span class="lines">@@ -29,3 +30,17 @@ </span><span class="cx"> def visit_column(self, column): </span><span class="cx"> if self.check_columns: </span><span class="cx"> column.table.accept_visitor(self) </span><ins>+ + +class CascadeOptions(object): + """keeps track of the options sent to relation().cascade""" + def __init__(self, arg=""): + values = sets.Set([c.strip() for c in arg.split(',')]) + self.delete_orphan = "delete-orphan" in values + self.delete = "delete" in values or self.delete_orphan + self.save_update = "save-update" in values + self.merge = "merge" in values + self.expunge = "expunge" in values + def __contains__(self, item): + return getattr(self, item.replace("-", "_"), False) + </ins><span class="cx">\ No newline at end of file </span></span></pre></div> <a id="sqlalchemybranchesschematestobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/branches/schema/test/objectstore.py (1315 => 1316)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/branches/schema/test/objectstore.py 2006-04-22 16:45:04 UTC (rev 1315) +++ sqlalchemy/branches/schema/test/objectstore.py 2006-04-22 17:36:30 UTC (rev 1316) </span><span class="lines">@@ -62,7 +62,7 @@ </span><span class="cx"> </span><span class="cx"> u = User() </span><span class="cx"> a = Address() </span><del>- s.add(u, a) </del><ins>+ s.save(u, a) </ins><span class="cx"> a.user = u </span><span class="cx"> #print repr(a.__class__._attribute_manager.get_history(a, 'user').added_items()) </span><span class="cx"> #print repr(u.addresses.added_items()) </span><span class="lines">@@ -410,7 +410,6 @@ </span><span class="cx"> </span><span class="cx"> self.assert_(len(objectstore.get_session().new) == 0) </span><span class="cx"> self.assert_(len(objectstore.get_session().dirty) == 0) </span><del>- self.assert_(len(objectstore.get_session().modified_lists) == 0) </del><span class="cx"> </span><span class="cx"> def testbasic(self): </span><span class="cx"> # save two users </span><span class="lines">@@ -421,7 +420,7 @@ </span><span class="cx"> u2 = User() </span><span class="cx"> u2.user_name = 'savetester2' </span><span class="cx"> </span><del>- objectstore.get_session().add(u) </del><ins>+ objectstore.get_session().save(u) </ins><span class="cx"> </span><span class="cx"> objectstore.get_session().flush(u) </span><span class="cx"> objectstore.get_session().flush() </span><span class="lines">@@ -440,6 +439,7 @@ </span><span class="cx"> </span><span class="cx"> # change first users name and save </span><span class="cx"> u.user_name = 'modifiedname' </span><ins>+ assert u in objectstore.get_session().dirty </ins><span class="cx"> objectstore.get_session().flush() </span><span class="cx"> </span><span class="cx"> # select both </span></span></pre> </div> </div> </body> </html> |