(Jan, please switch to a mail program that correctly sets the
In-Reply-To header... it's getting annoying to see a new thread each
time you reply...)
[email protected] wrote:
> How would one check those return values? I imagine that would be very awkward
> syntax.
>
It will get assigned to the name used for the implementation method, e.g.
@with_transaction(env, db)
def rename(db):
# ... transaction body ...
if rename is False:
print "rename failed"
# perform some related clean-up
I imagined this could be handy in some cases, but actually this will
have no direct equivalent when transitioning to the "with" syntax, so I
think we should in fact not do this.
Rather, the comparison with the "with" approach made me think we should
instead propagate the value returned by the implementation function,
otherwise we would have no way to communicate something from there to
the outside. When using "with", we will have no problem modifying local
variables from the calling context, something we can't do with the
decorator approach, for the same reasons as discussed in previous mail
regarding the "strange behavior" of db.
Also, this made me experiment with what we should do with exceptions
occurring in the transaction body (either propagate them or trap them).
We have 3 use cases:
1. we want to ignore any exception
2. we want to do something when a specific exception is raised in the
body of the transaction
3. we want the exception to be re-raised
If we always trap the exception, we would get 1. and for 2. we could add
a try/except block within the transaction body. But then we couldn't get 3.
If we always re-raise the exception, then we get 3. by default, and for
1. or 2. we would have to add a try/except.
So maybe the best approach would be a flag passed to the decorator (or
context manager) telling if the exception should be propagated, so we
could have 1. by default and not have to write a try/except in this
case, 2. by writing an exception handler inside the transaction body and
3. by setting that propagate flag to True.
See self-contained experiment, attached.
In this with_transaction.py file, there are 4 "do_rename_..." functions,
a pair demonstrating the decorator approach, with and without
propagating exceptions raised in the transaction body, and another pair
doing the same for the context manager approach. As one can see, both
approach are quite clean, the "with" syntax being the cleanest and less
verbose.
With something like that, we have a simple transition path to using
"with transaction() as db" in 0.13.
-- Christian
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Trac
Development" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/trac-dev?hl=en
-~----------~----~----~----~------~----~------~--~---
from __future__ import with_statement # this requires Python 2.5 or 2.6
class Connection(object):
def __init__(self, fail):
self._fail = fail
def execute(self):
if self._fail:
print '>> arghh!'
a = int('')
else:
print '>> execute something successfully...'
def commit(self):
print '>> commit transaction'
def rollback(self):
print '>> rollback transaction'
class Environment(object):
def __init__(self, fail):
self._fail = fail
def get_db_cnx(self):
print ">> Environment.get_db_cnx"
return Connection(self._fail)
def with_transaction(env, db, propagate_exc=False):
""" ... """
def wrap(fn):
if db:
try:
return fn(db)
except Exception, e:
if propagate_exc:
raise
else:
tmpdb = env.get_db_cnx()
try:
res = fn(tmpdb)
tmpdb.commit()
return res
except Exception, e:
tmpdb.rollback()
if propagate_exc:
raise
return wrap
class transaction(object):
def __init__(self, env, db, propagate_exc=False):
self._env = env
self._db = db
self._propagate_exc = propagate_exc
def __enter__(self):
if not self._db:
self._db = self._env.get_db_cnx()
self._env = None
return self._db
def __exit__(self, et, ev, tb):
if self._env is None:
if et is None:
self._db.commit()
else:
self._db.rollback()
return not self._propagate_exc
# Trac 0.12
def do_rename_deco(env, db=None):
new_name = None
@with_transaction(env, db)
def rename(db):
db.execute()
new_name = 'renamed' # local!
return new_name
new_name = rename
if new_name:
print '> it worked:', new_name
return True
else:
print '> rename failed'
def do_rename_deco_prop(env, db=None):
new_name = None
@with_transaction(env, db, propagate_exc=True)
def rename(db):
db.execute()
new_name = 'renamed' # local!
return new_name
new_name = rename
if new_name:
print '> it worked:', new_name
return True
else:
print '> rename failed'
# Trac 0.13
def do_rename_with(env, db=None):
new_name = None
with transaction(env, db) as db:
db.execute()
new_name = 'renamed' # modifies `new_name` above
if new_name:
print '> it worked:', new_name
return True
else:
print '> rename failed'
def do_rename_with_prop(env, db=None):
new_name = None
with transaction(env, db, propagate_exc=True) as db:
db.execute()
new_name = 'renamed' # modifies `new_name` above
if new_name:
print '> it worked:', new_name
return True
else:
print '> rename failed'
if __name__ == '__main__':
for do_rename in (do_rename_deco, do_rename_with):
for fail in (False, True):
env = Environment(fail)
print
print '== (fail=%r, do_rename=%r)' % (fail, do_rename)
print
print '-- testing automatic transaction'
do_rename(env)
print
print '-- testing explicit transaction'
db = env.get_db_cnx()
if do_rename(env, db) and do_rename(env, db):
db.commit()
else:
db.rollback()
for do_rename in (do_rename_deco_prop, do_rename_with_prop):
env = Environment(True)
print
print '== (fail=%r, do_rename=%r)' % (fail, do_rename)
print
print '-- testing automatic transaction (propagate_exc)'
try:
do_rename(env)
except ValueError:
print 'oops'
print
print '-- testing explicit transaction (propagate_exc)'
db = env.get_db_cnx()
try:
if do_rename(env, db) and do_rename(env, db):
db.commit()
else:
db.rollback()
except ValueError:
print 'oops'
db.rollback()