Coverage for portality/background.py: 95%
103 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-04 15:38 +0100
« prev ^ index » next coverage.py v6.4.2, created at 2022-08-04 15:38 +0100
1from flask_login import login_user
3from portality.core import app
4from portality import models
5from portality.bll import DOAJ
6from portality import constants
8import traceback
9from copy import deepcopy
12class BackgroundException(Exception):
13 pass
16class RetryException(Exception):
17 pass
20class BackgroundSummary(object):
21 def __init__(self, job_id, affected=None, error=None):
22 self.job_id = job_id
23 self.affected = affected if affected is not None else {}
24 self.error = error
26 def as_dict(self):
27 return {
28 "job_id" : self.job_id,
29 "affected" : self.affected,
30 "error" : self.error
31 }
34class BackgroundApi(object):
35 """
36 ~~BackgroundTasks:Feature~~
37 """
39 @classmethod
40 def execute(self, background_task):
41 # ~~->BackgroundTask:Process~~
42 # ~~->BackgroundJob:Model~~
43 job = background_task.background_job
44 ctx = None
45 acc = None
46 if job.user is not None:
47 ctx = app.test_request_context("/")
48 ctx.push()
49 # ~~-> Account:Model~~
50 acc = models.Account.pull(job.user) # FIXME: what happens when this is the "system" user
51 if acc is not None:
52 login_user(acc)
54 job.start()
55 job.add_audit_message("Job Started")
56 job.save()
58 try:
59 background_task.run()
60 except RetryException:
61 if job.reference is None:
62 job.reference = {}
63 retries = job.reference.get("retries", 0)
64 job.reference["retries"] = retries + 1
65 job.save()
66 raise
67 except Exception as e:
68 job.fail()
69 job.add_audit_message("Error in Job Run")
70 job.add_audit_message("Caught in job runner during run: " + traceback.format_exc())
71 job.add_audit_message("Job Run Completed")
73 job.add_audit_message("Cleanup Started")
74 try:
75 background_task.cleanup()
76 except Exception as e:
77 job.fail()
78 job.add_audit_message("Error in Cleanup Run")
79 job.add_audit_message("Caught in job runner during cleanup: " + traceback.format_exc())
80 job.add_audit_message("Job Cleanup Completed")
82 job.add_audit_message("Job Finished")
83 if not job.is_failed():
84 job.success()
85 job.save()
87 # trigger a status change event
88 jdata = deepcopy(job.data)
89 del jdata["audit"]
90 eventsSvc = DOAJ.eventsService()
91 eventsSvc.trigger(models.Event(constants.BACKGROUND_JOB_FINISHED, job.user, {
92 "job": jdata
93 }))
95 if ctx is not None:
96 ctx.pop()
99class BackgroundTask(object):
100 """
101 All background tasks should extend from this object and override at least the following methods:
103 - run
104 - cleanup
105 - prepare (class method)
107 ~~BackgroundTask:Process~~
108 """
110 __action__ = None
111 """ static member variable defining the name of this task """
113 def __init__(self, background_job):
114 self._background_job = background_job
116 @property
117 def background_job(self):
118 return self._background_job
120 def run(self):
121 """
122 Execute the task as specified by the background_job
123 :return:
124 """
125 raise NotImplementedError()
127 def cleanup(self):
128 """
129 Cleanup after a successful OR failed run of the task
130 :return:
131 """
132 raise NotImplementedError()
134 @classmethod
135 def prepare(cls, username, **kwargs):
136 """
137 Take an arbitrary set of keyword arguments and return an instance of a BackgroundJob,
138 or fail with a suitable exception
140 :param username: the user creating the job
141 :param kwargs: arbitrary keyword arguments pertaining to this task type
142 :return: a BackgroundJob instance representing this task
143 """
144 raise NotImplementedError()
146 @classmethod
147 def submit(cls, background_job):
148 """
149 Submit the specified BackgroundJob to the background queue
151 :param background_job: the BackgroundJob instance
152 :return:
153 """
154 raise NotImplementedError()
156 @classmethod
157 def get_param(cls, params, param_name, default=None):
158 return params.get('{}__{}'.format(cls.__action__, param_name), default)
160 @classmethod
161 def set_param(cls, params, param_name, value):
162 params['{}__{}'.format(cls.__action__, param_name)] = value
164 @classmethod
165 def set_reference(cls, refs, ref_name, value):
166 refs['{}__{}'.format(cls.__action__, ref_name)] = value
169class AdminBackgroundTask(BackgroundTask):
170 """~~AdminBackgroundTask:Process->BackgroundTask:Process~~"""
171 @classmethod
172 def check_admin_privilege(cls, username):
173 # ~~->Account:Model~~
174 a = models.Account.pull(username)
175 if a is None:
176 # very unlikely, as they would have had to log in to get to here..
177 raise BackgroundException('Admin account that is being used to prepare this job does not exist.')
179 if not a.has_role('admin'):
180 raise BackgroundException('Account {} is not permitted to run this background task.'.format(username))
182 @classmethod
183 def prepare(cls, username, **kwargs):
184 cls.check_admin_privilege(username)