File: Transactions.py

package info (click to toggle)
zope-zpatterns 0.4.3p2-2
  • links: PTS
  • area: main
  • in suites: woody
  • size: 476 kB
  • ctags: 814
  • sloc: python: 2,817; ansic: 310; makefile: 52; sh: 39
file content (326 lines) | stat: -rw-r--r-- 8,694 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
"""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





















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















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__ = ()

    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']


    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)


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...
    
    def _checkpoint(self):
        """Called during commit() phase of transaction/subtransaction;
           should do all the "real work" of committing"""
        pass

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

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

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

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

    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

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()

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

    # No need to define rollback, since cleanup always follows

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