Completed
Push — master ( 809ef6...84c53c )
by Bertrand
01:20 queued 22s
created

_patch_cursor()   F

Complexity

Conditions 13

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 13
c 2
b 1
f 0
dl 0
loc 23
rs 2.9787

1 Method

Rating   Name   Duplication   Size   Complexity  
C _patch_cursor_execute() 0 19 12

How to fix   Complexity   

Complexity

Complex classes like _patch_cursor() 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 functools import wraps
6
from time import time
7
8
from django.core.exceptions import EmptyResultSet
9
from django.db.backends.utils import CursorWrapper
10
from django.db.models.signals import post_migrate
11
from django.db.models.sql.compiler import (
12
    SQLCompiler, SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler,
13
)
14
from django.db.transaction import Atomic, get_connection
15
from django.utils.six import binary_type
16
17
from .api import invalidate
18
from .cache import cachalot_caches
19
from .settings import cachalot_settings
20
from .utils import (
21
    _get_query_cache_key, _get_table_cache_keys, _get_tables_from_sql,
22
    UncachableQuery, TUPLE_OR_LIST, is_cachable, filter_cachable,
23
)
24
25
26
WRITE_COMPILERS = (SQLInsertCompiler, SQLUpdateCompiler, SQLDeleteCompiler)
27
28
29
def _unset_raw_connection(original):
30
    def inner(compiler, *args, **kwargs):
31
        compiler.connection.raw = False
32
        out = original(compiler, *args, **kwargs)
33
        compiler.connection.raw = True
34
        return out
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 new_table_cache_keys:
46
        now = time()
47
        cache.set_many({k: now for k in new_table_cache_keys},
48
                       cachalot_settings.CACHALOT_TIMEOUT)
49
    elif cache_key in data:
50
        timestamp, result = data.pop(cache_key)
51
        table_times = data.values()
52
        if table_times and timestamp > max(table_times):
53
            return result
54
55
    result = execute_query_func()
56
    if isinstance(result, Iterable) and result.__class__ not in TUPLE_OR_LIST:
57
        result = list(result)
58
59
    cache.set(cache_key, (time(), result), cachalot_settings.CACHALOT_TIMEOUT)
60
61
    return result
62
63
64
def _patch_compiler(original):
65
    @wraps(original)
66
    @_unset_raw_connection
67
    def inner(compiler, *args, **kwargs):
68
        execute_query_func = lambda: original(compiler, *args, **kwargs)
69
        if not cachalot_settings.CACHALOT_ENABLED \
70
                or isinstance(compiler, WRITE_COMPILERS):
71
            return execute_query_func()
72
73
        try:
74
            cache_key = _get_query_cache_key(compiler)
75
            table_cache_keys = _get_table_cache_keys(compiler)
76
        except (EmptyResultSet, UncachableQuery):
77
            return execute_query_func()
78
79
        return _get_result_or_execute_query(
80
            execute_query_func,
81
            cachalot_caches.get_cache(db_alias=compiler.using),
82
            cache_key, table_cache_keys)
83
84
    return inner
85
86
87
def _patch_write_compiler(original):
88
    @wraps(original)
89
    @_unset_raw_connection
90
    def inner(write_compiler, *args, **kwargs):
91
        db_alias = write_compiler.using
92
        table = write_compiler.query.get_meta().db_table
93
        if is_cachable(table):
94
            invalidate(table, db_alias=db_alias,
95
                       cache_alias=cachalot_settings.CACHALOT_CACHE)
96
        return original(write_compiler, *args, **kwargs)
97
98
    return inner
99
100
101
def _patch_orm():
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 _patch_cursor():
108
    def _patch_cursor_execute(original):
109
        @wraps(original)
110
        def inner(cursor, sql, *args, **kwargs):
111
            out = original(cursor, sql, *args, **kwargs)
112
            if getattr(cursor.db, 'raw', True) \
113
                    and cachalot_settings.CACHALOT_INVALIDATE_RAW:
114
                if isinstance(sql, binary_type):
115
                    sql = sql.decode('utf-8')
116
                sql = sql.lower()
117
                if 'update' in sql or 'insert' in sql or 'delete' in sql \
118
                        or 'alter' in sql or 'create' in sql or 'drop' in sql:
119
                    tables = filter_cachable(
120
                        set(_get_tables_from_sql(cursor.db, sql)))
121
                    if tables:
122
                        invalidate(*tables, db_alias=cursor.db.alias,
123
                                   cache_alias=cachalot_settings.CACHALOT_CACHE)
124
            return out
125
126
        return inner
127
128
    CursorWrapper.execute = _patch_cursor_execute(CursorWrapper.execute)
129
    CursorWrapper.executemany = _patch_cursor_execute(CursorWrapper.executemany)
130
131
132
def _patch_atomic():
133
    def patch_enter(original):
134
        @wraps(original)
135
        def inner(self):
136
            cachalot_caches.enter_atomic(self.using)
137
            original(self)
138
139
        return inner
140
141
    def patch_exit(original):
142
        @wraps(original)
143
        def inner(self, exc_type, exc_value, traceback):
144
            needs_rollback = get_connection(self.using).needs_rollback
145
            original(self, exc_type, exc_value, traceback)
146
            cachalot_caches.exit_atomic(
147
                self.using, exc_type is None and not needs_rollback)
148
149
        return inner
150
151
    Atomic.__enter__ = patch_enter(Atomic.__enter__)
152
    Atomic.__exit__ = patch_exit(Atomic.__exit__)
153
154
155
def _invalidate_on_migration(sender, **kwargs):
156
    invalidate(*sender.get_models(), db_alias=kwargs['using'],
157
               cache_alias=cachalot_settings.CACHALOT_CACHE)
158
159
160
def patch():
161
    post_migrate.connect(_invalidate_on_migration)
162
163
    _patch_cursor()
164
    _patch_atomic()
165
    _patch_orm()
166