Logo Search packages:      
Sourcecode: zope-loginmanager version File versions

Transactions.py

00001 """Per-Transaction Cache Management

 ZPatterns objects sometimes need to reset certain cached data at transaction
 boundaries.  For example, DataSkins clear their attribute cache and
 state flags, while Racks clear their item cache.  To do this, they subclass
 from 'ZPatterns.Transactions.Kept', define a '__per_transaction_cache_attrs__'
 attribute listing the names of attributes they need cleared, and reference
 the 'self._v_Keeper' attribute just before changing any cached attributes.
 That's pretty much all that's required to make an object with per-transaction
 caching capabilities.

 **DO NOT USE THE DEPRECATED CLASSES**.  They contain some serious bugs based on
 a few critical misunderstandings of the Zope transaction state machinery, such
 as the woefully misguided belief that there is any way to know whether a Zope
 transaction is finished.  In fact, only the Zope transaction knows if it's
 finished, and it doesn't tell anybody else.  :(
 """

from ExtensionClass import Base
from ComputedAttribute import ComputedAttribute





















00042 class Keeper:
    """Resets an object's per-transaction cache attributes at txn boundaries

    Note that Keepers are created by Kept objects semi-automatically, and
    there is usually no need to create one manually.  A Keeper automatically
    registers itself with the Zope transaction upon creation, and instructs
    its Kept client to clear its per-transaction cache at all transaction
    boundaries.  Keeper methods are called only by the Zope transaction, so
    don't mess with them."""

    # tpc_begin, tpc_vote, commit, commit_sub: ignore
    # tpc_finish, tpc_abort, abort, abort_sub: clear

    def __init__(self,client):
        self.client = client
        get_transaction().register(self)

    def ClearCache(self,*args):
        self.client._clearPerTransactionCache()

    tpc_finish = tpc_abort = abort = abort_sub = ClearCache

    def tpc_begin(self,transaction,subtransaction=None): pass
    def commit(self,object,transaction): pass
    def tpc_vote(self,transaction):      pass
    def commit_sub(self,transaction):    pass















00083 class Kept(Base):
    """Thing which has a Keeper to clear its per-transaction cache.

    Objects derived from Kept should reference the 'self._v_Keeper'
    attribute whenever they need to flag that they have made changes to
    their cache that would require it to be cleared.  (Note that '_v_Keeper'
    is an *attribute*, not a method, and so should not be called, just
    referenced.)

    Once this has been done, the next transaction state transition
    that occurs (sub/main transaction commit or abort) will cause
    the object's Keeper to call for a cache reset.

    Subclasses of Kept should define a '__per_transaction_cache_attrs__'
    attribute as a sequence of attributes which they would like to have 
    deleted from their '__dict__' at reset time.
    """

    __per_transaction_cache_attrs__ = ()

00103     def _clearPerTransactionCache(self):
        """Get rid of stale data from previous transactions"""

        d=self.__dict__; have=d.has_key

        for a in self.__per_transaction_cache_attrs__:
            if have(a): del d[a]

        if have('_v_Keeper'): del d['_v_Keeper']


00114     def _v_Keeper(self):

        """Our transaction keeper, which resets the cache at txn boundaries"""

        v = self._v_Keeper = Keeper(self); return v


    _v_Keeper = ComputedAttribute(_v_Keeper)


00124 class Transactional:    
    """
    DEPRECATED Mix-in Adapter to simplify ZODB Transaction messages

    DO NOT USE THIS!!!
    
    Whenever your subclass does work which requires transactional behavior, 
    it should call self._register(), to ensure it is registered with the current
    transaction.  Your subclass will then be able to receive the following
    (translated) messages from the ZODB transaction:

    _checkpoint() -- 
        apply work done so far (this is where committing should mostly go)

    _revert() -- 
        abort work done since last checkpoint or since the transaction began
        if not yet checkpointed

    _rollback() -- 
        abort all checkpointed work (this call is always preceded by a _revert()
        call to handle any work which was not yet checkpointed, and is always
        followed by a _cleanup() call, so don't make it do the work of either.)

    _vote() -- 
        raise an exception here if you want to force the transaction to abort

    _finalize() -- 
        make all work done permanent (try not to refer to objects' states here)

    _cleanup() --
        called whenever a transaction is terminated, whatever its outcome

    Your revert, rollback, finalize, and cleanup methods should not raise any 
    exceptions.  An untrapped exception from _finalize() (or _cleanup() after
    _finalize()) will put the entire ZODB application into "hosed" state which
    can only be undone by an application restart.  The ZODB transaction will
    in most cases silently toss exceptions from your _revert() and _rollback()
    routines, so it would be a good idea to log them somewhere.
    """

    _v_registered = None      # Are we registered w/a transaction?
    def _register(self):
        if self._v_registered: return
        get_transaction().register(Reporter(self))
        self._v_registered = 1

    def _unregister(self):
        try:
            del self._v_registered
        except:
            pass

    # From here down, override as you see fit...
    
00178     def _checkpoint(self):
        """Called during commit() phase of transaction/subtransaction;
           should do all the "real work" of committing"""
        pass

00183     def _revert(self):
        """Do the real work of aborting anything here"""
        pass

00187     def _rollback(self):
        """Called during tpc_abort - should do as little as possible"""
        pass

00191     def _vote(self):
        """Opportunity to raise an error to prevent commit"""
        pass

00195     def _finalize(self):
        """Called during tpc_finish - should do as little as possible"""
        pass

00199     def _cleanup(self):
        """Called after transaction is effectively *over*, so don't
           do anything here that either 1) isn't idempotent, or 2)
           requires knowledge about the state/data of the transaction."""
        pass


class Reporter:
    # DEPRECATED Event translator for Transactionals
    
    tpc_entered = 0        # Are we in tpc yet?
    _sub = None            # Active subtransaction?

    def __init__(self, client):
        self.client = client

    def tpc_begin(self, transaction, subtransaction=None):
        self.tpc_entered = 1
        self._sub = subtransaction

    def tpc_abort(self,transaction):
        try:
            self.client._revert()
            self.client._rollback()
        finally:
            self.end_tran()

    def tpc_vote(self,transaction):
        self.client._vote()

    def tpc_finish(self,transaction):
        if self._sub: return
        try:
            self.client._finalize()
        finally:
            self.end_tran()

    def commit_sub(self, transaction):
        self._sub = None

    def abort_sub(self, transaction):
        self._sub = None
        self.tpc_abort()

    def commit(self, object, transaction):
        self.client._checkpoint()


    def abort(self, object, transaction):
        try:
            self.client._revert()
        finally:
            if not self.tpc_entered:
                # Abort before tpc_begin() means transaction aborted early
                self.end_tran()


    def end_tran(self):
        try:
            self.client._cleanup()
        finally:
            self.client._unregister()



























from UserDict import UserDict

00290 class TransientMapping(Transactional, UserDict):
    """DEPRECATED A mapping that clears itself after every transaction"""

    saved = None

    def __setitem__(self, key, item):
        self._register()
        UserDict.__setitem__.im_func(self, key, item)

    def __delitem__(self, key):
        self._register()
        UserDict.__delitem__.im_func(self, key)

    def update(self, dict):
        self._register()
        UserDict.update.im_func(self, dict)

    def clear(self):
        self._register()
        UserDict.clear.im_func(self)

    def copy(self):
        return self.data.copy()

00314     def _checkpoint(self):
        self.saved = self.data.copy()
        
00317     def _revert(self):
        if self.saved:
            self.data = self.saved
            del self.saved

    # No need to define rollback, since cleanup always follows

00324     def _cleanup(self):
        UserDict.clear.im_func(self)
        Transactional._cleanup.im_func(self)

Generated by  Doxygen 1.6.0   Back to index