File: __init__.py

package info (click to toggle)
python-repoze.tm2 2.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 264 kB
  • sloc: python: 422; makefile: 64
file content (161 lines) | stat: -rw-r--r-- 4,845 bytes parent folder | download
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
# repoze TransactionManager WSGI middleware
import sys

import transaction

ekey = 'repoze.tm.active'


def reraise(tp, value, tb=None):
    if value.__traceback__ is not tb:  # pragma: NO COVER
        raise value.with_traceback(tb)
    raise value


class TM:
    """ Transaction management WSGI middleware """
    def __init__(self, application, commit_veto=None):
        self.application = application
        self.commit_veto = commit_veto
        self.transaction = transaction # for testing
        
    def __call__(self, environ, start_response):
        transaction = self.transaction
        environ[ekey] = True
        transaction.begin()
        ctx = {}

        def save_status_and_headers(status, headers, exc_info=None):
            ctx.update(status=status, headers=headers)
            return start_response(status, headers, exc_info)

        try:
            for chunk in self.application(environ, save_status_and_headers):
                yield chunk
        except Exception:
            """Saving the exception"""
            try:
                type_, value, tb = sys.exc_info()
                self.abort()
                reraise(type_, value, tb)
            finally:
                del type_, value, tb

        # ZODB 3.8 + has isDoomed
        if hasattr(transaction, 'isDoomed') and transaction.isDoomed():
            self.abort()
        else:
            if self.commit_veto is not None:
                try:
                    status, headers = ctx['status'], ctx['headers']
                    veto = self.commit_veto(environ, status, headers)
                except:
                    self.abort()
                    raise

                if veto:
                    self.abort()
                else:
                    self.commit()

            else:
                self.commit()

    def commit(self):
        t = self.transaction.get()
        t.commit()
        after_end.cleanup(t)

    def abort(self):
        t = self.transaction.get()
        t.abort()
        after_end.cleanup(t)

def isActive(environ):
    """ Return True if the ``repoze.tm.active`` key is in the WSGI
    environment passed as ``environ``, otherwise return ``False``."""
    if ekey in environ:
        return True
    return False

# Callback registry API helper class
class AfterEnd:
    """ Callback registry API helper class.  Use the singleton instance
    ``repoze.tm.after_end`` when possible."""
    key = '_repoze_tm_afterend'
    def register(self, func, txn):
        funcs = getattr(txn, self.key, None)
        if funcs is None:
            funcs = []
            setattr(txn, self.key, funcs)
        funcs.append(func)

    def unregister(self, func, txn):
        funcs = getattr(txn, self.key, None)
        if funcs is None:
            return
        new = []
        for f in funcs:
            if f is func:
                continue
            new.append(f)
        if new:
            setattr(txn, self.key, new)
        else:
            delattr(txn, self.key)

    def cleanup(self, txn):
        funcs = getattr(txn, self.key, None)
        if funcs is not None:
            for func in funcs:
                func()
            delattr(txn, self.key)

# singleton, importable by other modules
after_end = AfterEnd()

def default_commit_veto(environ, status, headers):
    """
    When used as a commit veto, the logic in this function will cause the
    transaction to be committed if:

    - An ``X-Tm`` header with the value ``commit`` exists.

    If an ``X-Tm`` header with the value ``commit`` does not exist, the
    transaction will be aborted, if:

    - An ``X-Tm`` header with the value ``abort`` (or any value other than
      ``commit``) exists.

    - An ``X-Tm-Abort`` header exists with any value (for backwards
      compatibility; prefer ``X-Tm=abort`` in new code).

    - The status code starts with ``4`` or ``5``.

    Otherwise the transaction will be committed by default.
    """
    abort_compat = False
    for header_name, header_value in headers:
        header_name = header_name.lower()
        if header_name == 'x-tm':
            header_value = header_value.lower()
            if header_value == 'commit':
                return False
            return True
        # x-tm always honored before x-tm-abort 1.0b1 compatibility
        if header_name == 'x-tm-abort':
            abort_compat = True
    if abort_compat:
        return True
    for bad in ('4', '5'):
        if status.startswith(bad):
            return True
    return False

def make_tm(app, global_conf, commit_veto=None):
    """ Paste filter_app_factory entry point for creation of a TM middleware."""
    from pkg_resources import EntryPoint
    if commit_veto is not None:
        commit_veto = EntryPoint.parse('x=%s' % commit_veto).resolve()
    return TM(app, commit_veto)