Completed
Push — master ( 366176...6e429f )
by Bertrand
53s
created

_patch_cursor_execute()   D

Complexity

Conditions 11

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 2 Features 0
Metric Value
cc 11
c 4
b 2
f 0
dl 0
loc 22
rs 4.0714

How to fix   Complexity   

Complexity

Complex classes like _patch_cursor_execute() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# coding: utf-8
2
3
from __future__ import unicode_literals
4
from collections import Iterable
5
from time import time
6
7
from django.db.backends.utils import CursorWrapper
8
from django.db.models.query import EmptyResultSet
9
from django.db.models.signals import post_migrate
10
from django.db.models.sql.compiler import (
11
    SQLCompiler, SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler,
12
)
13
from django.db.transaction import Atomic, get_connection
14
from django.utils.six import binary_type, wraps
15
16
from .api import invalidate
17
from .cache import cachalot_caches
18
from .settings import cachalot_settings, ITERABLES
19
from .utils import (
20
    _get_table_cache_keys, _get_tables_from_sql,
21
    UncachableQuery, is_cachable, filter_cachable,
22
)
23
24
25
WRITE_COMPILERS = (SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
26
27
28
def _unset_raw_connection(original):
29
    def inner(compiler, *args, **kwargs):
30
        compiler.connection.raw = False
31
        try:
32
            return original(compiler, *args, **kwargs)
33
        finally:
34
            compiler.connection.raw = True
35
    return inner
36
37
38
def _get_result_or_execute_query(execute_query_func, cache,
39
                                 cache_key, table_cache_keys):
40
    data = cache.get_many(table_cache_keys + [cache_key])
41
42
    new_table_cache_keys = set(table_cache_keys)
43
    new_table_cache_keys.difference_update(data)
44
45
    if not new_table_cache_keys and cache_key in data:
46
        timestamp, result = data.pop(cache_key)
47
        if timestamp >= max(data.values()):
48
            return result
49
50
    result = execute_query_func()
51
    if result.__class__ not in ITERABLES and isinstance(result, Iterable):
52
        result = list(result)
53
54
    now = time()
55
    to_be_set = {k: now for k in new_table_cache_keys}
56
    to_be_set[cache_key] = (now, result)
57
    cache.set_many(to_be_set, cachalot_settings.CACHALOT_TIMEOUT)
58
59
    return result
60
61
62
def _patch_compiler(original):
63
    @wraps(original)
64
    @_unset_raw_connection
65
    def inner(compiler, *args, **kwargs):
66
        execute_query_func = lambda: original(compiler, *args, **kwargs)
67
        db_alias = compiler.using
68
        if db_alias not in cachalot_settings.CACHALOT_DATABASES \
69
                or isinstance(compiler, WRITE_COMPILERS):
70
            return execute_query_func()
71
72
        try:
73
            cache_key = cachalot_settings.CACHALOT_QUERY_KEYGEN(compiler)
74
            table_cache_keys = _get_table_cache_keys(compiler)
75
        except (EmptyResultSet, UncachableQuery):
76
            return execute_query_func()
77
78
        return _get_result_or_execute_query(
79
            execute_query_func,
80
            cachalot_caches.get_cache(db_alias=db_alias),
81
            cache_key, table_cache_keys)
82
83
    return inner
84
85
86
def _patch_write_compiler(original):
87
    @wraps(original)
88
    @_unset_raw_connection
89
    def inner(write_compiler, *args, **kwargs):
90
        db_alias = write_compiler.using
91
        table = write_compiler.query.get_meta().db_table
92
        if is_cachable(table):
93
            invalidate(table, db_alias=db_alias,
94
                       cache_alias=cachalot_settings.CACHALOT_CACHE)
95
        return original(write_compiler, *args, **kwargs)
96
97
    return inner
98
99
100
def _patch_orm():
101
    if cachalot_settings.CACHALOT_ENABLED:
102
        SQLCompiler.execute_sql = _patch_compiler(SQLCompiler.execute_sql)
103
    for compiler in WRITE_COMPILERS:
104
        compiler.execute_sql = _patch_write_compiler(compiler.execute_sql)
105
106
107
def _unpatch_orm():
108
    if hasattr(SQLCompiler.execute_sql, '__wrapped__'):
109
        SQLCompiler.execute_sql = SQLCompiler.execute_sql.__wrapped__
110
    for compiler in WRITE_COMPILERS:
111
        compiler.execute_sql = compiler.execute_sql.__wrapped__
112
113
114
def _patch_cursor():
115
    def _patch_cursor_execute(original):
116
        @wraps(original)
117
        def inner(cursor, sql, *args, **kwargs):
118
            try:
119
                return original(cursor, sql, *args, **kwargs)
120
            finally:
121
                connection = cursor.db
122
                if getattr(connection, 'raw', True):
123
                    if isinstance(sql, binary_type):
124
                        sql = sql.decode('utf-8')
125
                    sql = sql.lower()
126
                    if 'update' in sql or 'insert' in sql or 'delete' in sql \
127
                            or 'alter' in sql or 'create' in sql \
128
                            or 'drop' in sql:
129
                        tables = filter_cachable(
130
                            _get_tables_from_sql(connection, sql))
131
                        if tables:
132
                            invalidate(
133
                                *tables, db_alias=connection.alias,
134
                                cache_alias=cachalot_settings.CACHALOT_CACHE)
135
136
        return inner
137
138
    if cachalot_settings.CACHALOT_INVALIDATE_RAW:
139
        CursorWrapper.execute = _patch_cursor_execute(CursorWrapper.execute)
140
        CursorWrapper.executemany = \
141
            _patch_cursor_execute(CursorWrapper.executemany)
142
143
144
def _unpatch_cursor():
145
    if hasattr(CursorWrapper.execute, '__wrapped__'):
146
        CursorWrapper.execute = CursorWrapper.execute.__wrapped__
147
        CursorWrapper.executemany = CursorWrapper.executemany.__wrapped__
148
149
150
def _patch_atomic():
151
    def patch_enter(original):
152
        @wraps(original)
153
        def inner(self):
154
            cachalot_caches.enter_atomic(self.using)
155
            original(self)
156
157
        return inner
158
159
    def patch_exit(original):
160
        @wraps(original)
161
        def inner(self, exc_type, exc_value, traceback):
162
            needs_rollback = get_connection(self.using).needs_rollback
163
            try:
164
                original(self, exc_type, exc_value, traceback)
165
            finally:
166
                cachalot_caches.exit_atomic(
167
                    self.using, exc_type is None and not needs_rollback)
168
169
        return inner
170
171
    Atomic.__enter__ = patch_enter(Atomic.__enter__)
172
    Atomic.__exit__ = patch_exit(Atomic.__exit__)
173
174
175
def _unpatch_atomic():
176
    Atomic.__enter__ = Atomic.__enter__.__wrapped__
177
    Atomic.__exit__ = Atomic.__exit__.__wrapped__
178
179
180
def _invalidate_on_migration(sender, **kwargs):
181
    invalidate(*sender.get_models(), db_alias=kwargs['using'],
182
               cache_alias=cachalot_settings.CACHALOT_CACHE)
183
184
185
def patch():
186
    post_migrate.connect(_invalidate_on_migration)
187
188
    _patch_cursor()
189
    _patch_atomic()
190
    _patch_orm()
191
192
193
def unpatch():
194
    post_migrate.disconnect(_invalidate_on_migration)
195
196
    _unpatch_cursor()
197
    _unpatch_atomic()
198
    _unpatch_orm()
199