Completed
Push — master ( 726486...b38261 )
by Paolo
19s queued 14s
created

common.tasks.BatchUpdateMixin.batch_update()   A

Complexity

Conditions 5

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 19
rs 9.2333
c 0
b 0
f 0
cc 5
nop 4
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
"""
4
Created on Tue Jan 15 16:42:24 2019
5
6
@author: Paolo Cozzi <[email protected]>
7
"""
8
9
import redis
10
import logging
11
12
from contextlib import contextmanager
13
from celery.five import monotonic
14
15
from django.conf import settings
16
17
from submissions.helpers import send_message
18
from validation.helpers import construct_validation_message
19
from common.constants import NEED_REVISION, ERROR
20
21
# Lock expires in 10 minutes
22
LOCK_EXPIRE = 60 * 10
23
24
# Get an instance of a logger
25
logger = logging.getLogger(__name__)
26
27
28
class BatchFailurelMixin():
29
    """Common mixin for batch task failure. Need to setup ``batch_type``
30
    (update/delete) and ``model_type`` (animal/sample)
31
    """
32
33
    batch_type = None
34
    model_type = None
35
    submission_cls = None
36
37
    # Ovverride default on failure method
38
    # This is not a failed validation for a wrong value, this is an
39
    # error in task that mean an error in coding
40
    def on_failure(self, exc, task_id, args, kwargs, einfo):
41
        logger.error('{0!r} failed: {1!r}'.format(task_id, exc))
42
43
        submission_id = args[0]
44
45
        logger.error(
46
            ("%s called with %s" % (self.name, args))
47
        )
48
49
        # get submission object
50
        submission_obj = self.submission_cls.objects.get(pk=submission_id)
51
52
        # mark submission with ERROR
53
        submission_obj.status = ERROR
54
        submission_obj.message = (
55
            "Error in %s batch %s: %s" % (
56
                self.model_type, self.batch_type, str(exc)))
57
        submission_obj.save()
58
59
        send_message(submission_obj)
60
61
        # send a mail to the user with the stacktrace (einfo)
62
        submission_obj.owner.email_user(
63
            "Error in %s batch %s for submission: %s" % (
64
                self.model_type, self.batch_type, submission_obj.id),
65
            ("Something goes wrong in batch %s for %ss. Please report "
66
             "this to InjectTool team\n\n %s" % (
67
                self.model_type, self.batch_type, str(einfo))),
68
        )
69
70
        # TODO: submit mail to admin
71
72
73
class BatchUpdateMixin:
74
    """Mixin to do batch update of fields to fix validation"""
75
76
    item_cls = None
77
    submission_cls = None
78
79
    def batch_update(self, submission_id, ids, attribute):
80
        for id_, value in ids.items():
81
            if value == '' or value == 'None':
82
                value = None
83
84
            item_object = self.item_cls.objects.get(pk=id_)
85
86
            if getattr(item_object, attribute) != value:
87
                setattr(item_object, attribute, value)
88
                item_object.save()
89
90
        # Update submission
91
        submission_obj = self.submission_cls.objects.get(pk=submission_id)
92
        submission_obj.status = NEED_REVISION
93
        submission_obj.message = "Data updated, try to rerun validation"
94
        submission_obj.save()
95
96
        send_message(
97
            submission_obj, construct_validation_message(submission_obj)
98
        )
99
100
101
@contextmanager
102
def redis_lock(lock_id, blocking=False):
103
    # read parameters from settings
104
    REDIS_CLIENT = redis.StrictRedis(
105
        host=settings.REDIS_HOST,
106
        port=settings.REDIS_PORT,
107
        db=settings.REDIS_DB)
108
109
    timeout_at = monotonic() + LOCK_EXPIRE - 3
110
111
    lock = REDIS_CLIENT.lock(lock_id, timeout=LOCK_EXPIRE)
112
    status = lock.acquire(blocking=blocking)
113
114
    try:
115
        yield status
116
117
    finally:
118
        # we take advantage of using add() for atomic locking
119
        if monotonic() < timeout_at and status:
120
            # don't release the lock if we exceeded the timeout
121
            # to lessen the chance of releasing an expired lock
122
            # owned by someone else
123
            # also don't release the lock if we didn't acquire it
124
            lock.release()
125