[TransWarp] Input wanted: Streams, Factories, sessions,
etc.
Phillip J. Eby
pje at telecommunity.com
Tue Nov 26 19:52:31 EST 2002
At 02:05 PM 11/18/02 -0500, Phillip J. Eby wrote:
>>Also, in writing the above, I'm thinking that maybe we should make the
>>convenience functions a bit more convenient, and change them to
>>storage.beginTxn, etc. We already use the "txn" abreviation elsewhere
>>(txnTime, joinTxn, etc), and it shouldn't cause much/any pain to anyone
>>if we change it now, before anyone is really using it...
storage.begin()/commit()/abort() are now available as aliases for the long
forms.
>While on that subject, we should also replace the current 'txnSvc'
>attribute in TransactionComponents to be 'joinedTxn', and make 'txnSvc' a
>binding.Acquire(storage.ITransactionService). In this way, one can do
>something like this:
>
>thing = SomeComponentClas(txnSvc=storage.TransactionService())
>storage.beginTxn(thing)
>thing.doSomething()
>storage.commitTxn(thing)
The above is now available as well; txnSvc can be passed as a keyword
argument to any subclass of TransactionComponent, and it will supply that
service to its child components as well.
>It would be a quick-and-dirty way to "create another TransactionService,
>etc." for your use case above. Perhaps we could even have a way to do this:
>
># Thing is created from given class, and given a separate transaction service,
># and a proxy to the new thing is returned
>thingProxy = storage.transacted(SomeComponentClass, args..., kwargs...)
>
># methods from the proxy do a begin and commit on the wrapped item
>thingProxy.doSomething()
>
>
>Or, more specifically:
>
>storage.transacted(
> naming.lookup("smtp://foo.bar").session,
> # & session args/kw, if any
>).sendmail(
> #... sendmail args
>)
>
>I'm not sure what I think about this, since it allows separation of
>context; i.e. if you take the proxy and do something with it, you might
>not remember it's operating within a different transaction scope, or that
>it transacts every method call.
The new plan to deal with this scenario is to create two new base classes:
AutoCommitter and AutoCommitOnly. Both will accept an 'autocommit' keyword
argument. AutoCommitter.__init__ will check this value, and if true, it
will set its 'txnSvc' to a new TransactionService if one was not
supplied. There will be a method wrapper which can be used to wrap methods
of AutoCommitters as "autocommitting". Autocommitting methods will wrap
themselves in a beginTransaction/commitTransaction pair, *if and only if*
self.autocommit is set *and* the component's 'txnSvc' is not already in a
transaction. (Also, if an error occurs during execution of the method,
abortTransaction() will be called if the wrapper issued a beginTransaction().)
This algorithm allows AutoCommitters' methods to either participate in a
larger transaction, or be used on their own as a self-contained
transaction. Such methods can even call one another, without fear of
committing or aborting too soon.
The AutoCommitOnly base class is a bit different. It will simply *require*
that the 'autocommit' keyword argument be supplied, and that it must have a
true value! The purpose of this base class is to be used with components
whose services are inherently non-transactional. For example, an SMTP
mail-sending operation isn't really transactional, and can't be made so
without a lot of overhead. So an SMTP factory's 'session()' method could
simply require that you pass a true value for autocommit, signalling that
you know and intend that its behavior should be "standalone". This allows
code to safely use a common interface, since it won't accidentally invoke a
non-transactional component when transactional semantics are intended/expected.
Anyway, the plan is such that a "standalone sendmail" operation might look
like this:
naming.lookup("smtp://foo.bar").session(autocommit=1).sendmail(
#... sendmail args go here
)
Which doesn't seem too bad. The nice thing about this is that it's then
interchangeable with providers such as our 'queuesmtp' package, which
simply queues outgoing mail and supports transactions, as long as the app
wants immediate mail sending. If the app wants transactional mail sending,
it doesn't set 'autocommit' in the session args, but then it can only use
providers that support a transactional 'sendmail' function.
Naturally, these considerations will be relevant for many other kinds of
objects. It's also likely that in anything longer than a short script, one
will do things more like:
class Thing(binding.Component):
mailer = binding.bindTo('alertsMailer')
def alert(self):
self.mailer.session(autocommit=1).sendmail(
#... etc.
)
Where of course 'alertsMailer' is looked up in the default naming service
in order to get the actual provider address.
More information about the PEAK
mailing list