Statistics
| Branch: | Tag: | Revision:

root / snf-common / synnefo / lib / db / xctx.py @ 7d95385b

History | View | Annotate | Download (4.1 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):
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, using=None, **kwargs):
75
        self.db         = (using if using is not None
76
                           else transaction.DEFAULT_DB_ALIAS)
77
        self.ctx_class  = ctx
78
        self.ctx_kwargs = kwargs
79

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

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

    
106
    def __exit__(self, type, value, traceback):
107
        db = self.db
108
        try:
109
            if value is not None: # exception
110
                if transaction.is_dirty(using=db):
111
                    transaction.rollback(using=db)
112
            else:
113
                if transaction.is_dirty(using=db):
114
                    if self.ctx.is_marked_rollback():
115
                        transaction.rollback(using=db)
116
                    else:
117
                        try:
118
                            transaction.commit(using=db)
119
                        except:
120
                            transaction.rollback(using=db)
121
                            raise
122
                        else:
123
                            self.ctx.postprocess()
124
        finally:
125
            transaction.leave_transaction_management(using=db)