Statistics
| Branch: | Tag: | Revision:

root / snf-common / synnefo / lib / db / xctx.py @ 26e15cff

History | View | Annotate | Download (4.8 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from django.db import transaction
35

    
36
# USAGE
37
# =====
38
# @transaction_context()
39
# def a_view(args, ctx=None):
40
#     ...
41
#     if ctx:
42
#         ctx.mark_rollback()
43
#     ...
44
#     return http response
45
#
46
# OR (more cleanly)
47
#
48
# def a_view(args):
49
#     with transaction_context() as ctx:
50
#         ...
51
#         ctx.mark_rollback()
52
#         ...
53
#         return http response
54

    
55
def transaction_context(**kwargs):
56
    return TransactionHandler(ctx=TransactionContext, **kwargs)
57

    
58

    
59
class TransactionContext(object):
60
    def __init__(self, **kwargs):
61
        self._rollback = False
62

    
63
    def mark_rollback(self):
64
        self._rollback = True
65

    
66
    def is_marked_rollback(self):
67
        return self._rollback
68

    
69
    def postprocess(self):
70
        pass
71

    
72

    
73
class TransactionHandler(object):
74
    def __init__(self, ctx=None, allow_postprocess=True, using=None, **kwargs):
75
        self.using             = using
76
        self.db                = (using if using is not None
77
                                  else transaction.DEFAULT_DB_ALIAS)
78
        self.ctx_class         = ctx
79
        self.ctx_kwargs        = kwargs
80
        self.allow_postprocess = allow_postprocess
81

    
82
    def __call__(self, func):
83
        def wrap(*args, **kwargs):
84
            ctx = self.__enter__()
85
            kwargs['ctx'] = ctx
86
            typ = value = trace = None
87
            result = None
88
            try:
89
                result = func(*args, **kwargs)
90
            except Exception as e:
91
                typ = type(e)
92
                value = e
93
                trace = None
94
            finally:
95
                silent = self.__exit__(typ, value, trace)
96
                if not silent and value:
97
                    raise value
98
            return result
99
        return wrap
100

    
101
    def __enter__(self):
102
        db = self.db
103
        transaction.enter_transaction_management(using=db)
104
        transaction.managed(True, using=db)
105
        self.ctx = self.ctx_class(self.ctx_kwargs)
106
        return self.ctx
107

    
108
    def __exit__(self, type, value, traceback):
109
        db = self.db
110
        trigger_postprocess = False
111
        try:
112
            if value is not None: # exception
113
                if transaction.is_dirty(using=db):
114
                    transaction.rollback(using=db)
115
            else:
116
                if transaction.is_dirty(using=db):
117
                    if self.ctx.is_marked_rollback():
118
                        transaction.rollback(using=db)
119
                    else:
120
                        try:
121
                            transaction.commit(using=db)
122
                        except:
123
                            transaction.rollback(using=db)
124
                            raise
125
                        else:
126
                            trigger_postprocess = True
127
                else:
128
                    # postprocess,
129
                    # even if there was nothing to commit
130
                    trigger_postprocess = True
131
        finally:
132
            transaction.leave_transaction_management(using=db)
133

    
134
            # checking allow_postprocess is needed
135
            # in order to avoid endless recursion
136
            if trigger_postprocess and self.allow_postprocess:
137
                with TransactionHandler(ctx=self.ctx_class,
138
                                        allow_postprocess=False,
139
                                        using=self.using,
140
                                        **self.ctx_kwargs):
141
                    self.ctx.postprocess()