forked from ourresearch/total-impact-webapp
/
tasks.py
293 lines (232 loc) · 10.1 KB
/
tasks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
import os
import datetime
import time
import logging
import celery
from celery.decorators import task
from celery.signals import task_postrun, task_prerun, task_failure, worker_process_init
from sqlalchemy.exc import IntegrityError, DataError, InvalidRequestError
from sqlalchemy.orm.exc import FlushError
from totalimpactwebapp.user import User
from totalimpactwebapp import db
from totalimpactwebapp.json_sqlalchemy import JSONAlchemy
from totalimpactwebapp.user import remove_duplicates_from_user
from totalimpactwebapp.card_generate import *
from totalimpactwebapp import notification_report
from totalimpactwebapp import emailer
logger = logging.getLogger("webapp.tasks")
@task_postrun.connect()
def task_postrun_handler(*args, **kwargs):
# close db session
db.session.remove()
@task_failure.connect
def task_failure_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, retval=None, state=None, **kwds):
try:
logger.error(u"Celery task FAILED on task_id={task_id}, {args}".format(
task_id=task_id, args=args))
except KeyError:
pass
class CeleryStatus(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
url_slug = db.Column(db.Text)
task_uuid = db.Column(db.Text)
task_name = db.Column(db.Text)
state = db.Column(db.Text)
args = db.Column(JSONAlchemy(db.Text))
kwargs = db.Column(JSONAlchemy(db.Text))
result = db.Column(JSONAlchemy(db.Text))
run = db.Column(db.DateTime())
def __init__(self, **kwargs):
logger.debug(u"new CeleryStatus {kwargs}".format(
kwargs=kwargs))
self.run = datetime.datetime.utcnow()
super(CeleryStatus, self).__init__(**kwargs)
def __repr__(self):
return u'<CeleryStatus {user_id} {task_name}>'.format(
user_id=self.user_id,
task_name=self.task_name)
class TaskAlertIfFail(celery.Task):
def __call__(self, *args, **kwargs):
"""In celery task this function call the run method, here you can
set some environment variable before the run of the task"""
# logger.info(u"Starting to run")
return self.run(*args, **kwargs)
def on_failure(self, exc, task_id, args, kwargs, einfo):
if not "user_id" in args:
user_id = "unknown"
logger.error(u"Celery task failed on {task_name}, user {user_id}, task_id={task_id}".format(
task_name=self.name, user_id=user_id, task_id=task_id))
# from http://stackoverflow.com/questions/6393879/celery-task-and-customize-decorator
class TaskThatSavesState(celery.Task):
def __call__(self, *args, **kwargs):
"""In celery task this function call the run method, here you can
set some environment variable before the run of the task"""
# logger.info(u"Starting to run")
return self.run(*args, **kwargs)
def after_return(self, status, retval, task_id, args, kwargs, einfo):
#exit point of the task whatever is the state
# logger.info(u"Ending run")
user_id = None
url_slug = None
args_to_save = []
for arg in args:
args_to_save.append(u"{user_object}".format(user_object=arg))
if isinstance(arg, User):
user_id = arg.id
url_slug = arg.url_slug
celery_status = CeleryStatus(
task_name = self.name,
task_uuid = task_id,
user_id = user_id,
url_slug = url_slug,
state = status,
args = args_to_save,
kwargs = kwargs,
# result = retval #causing seriializaion problems when failures
)
db.session.add(celery_status)
try:
db.session.commit()
except InvalidRequestError:
db.session.rollback()
db.session.commit()
class ProfileDeets(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
tiid = db.Column(db.Text)
provider = db.Column(db.Text)
metric = db.Column(db.Text)
current_raw = db.Column(JSONAlchemy(db.Text))
diff = db.Column(JSONAlchemy(db.Text))
diff_days = db.Column(db.Text)
metrics_collected_date = db.Column(db.DateTime())
deets_collected_date = db.Column(db.DateTime())
def __init__(self, **kwargs):
logger.debug(u"new ProfileDeets {kwargs}".format(
kwargs=kwargs))
self.deets_collected_date = datetime.datetime.utcnow()
super(ProfileDeets, self).__init__(**kwargs)
def __repr__(self):
return u'<ProfileDeets {user_id} {tiid}>'.format(
user_id=self.user_id,
tiid=self.tiid)
# @task(ignore_result=True, base=TaskThatSavesState)
# def add_profile_deets(user):
# product_dicts = products_list.prep(
# user.products,
# include_headings=False,
# display_debug=True
# )
# for product in product_dicts:
# tiid = product["_id"]
# for full_metric_name in product["metrics"]:
# if "historical_values" in product["metrics"][full_metric_name]:
# hist = product["metrics"][full_metric_name]["historical_values"]
# (provider, metric) = full_metric_name.split(":")
# if hist["diff"]["raw"]:
# profile_deets = ProfileDeets(
# user_id=user.id,
# tiid=tiid,
# provider=provider,
# metric=metric,
# current_raw=hist["current"]["raw"],
# metrics_collected_date=hist["current"]["collected_date"],
# diff=hist["diff"]["raw"],
# diff_days=hist["diff"]["days"]
# )
# db.session.add(profile_deets)
# db.session.commit()
@task(ignore_result=True, base=TaskThatSavesState)
def deduplicate(user):
removed_tiids = []
try:
removed_tiids = remove_duplicates_from_user(user.id)
logger.debug(removed_tiids)
except Exception as e:
logger.warning(u"EXCEPTION!!!!!!!!!!!!!!!! deduplicating")
return removed_tiids
@task(base=TaskAlertIfFail)
def send_email_if_new_diffs(user_id):
user = db.session.query(User).get(user_id)
status = "started"
now = datetime.datetime.utcnow()
logger.debug(u"in send_email_if_new_diffs for {url_slug}".format(url_slug=user.url_slug))
latest_diff_timestamp = user.latest_diff_ts
status = "checking diffs"
if not user.last_email_check or (latest_diff_timestamp > user.last_email_check.isoformat()):
logger.info(u"has diffs since last email check! calling send_email report for {url_slug}".format(url_slug=user.url_slug))
send_email_report(user, now)
status = "email sent"
else:
logger.info(u"not sending, no new diffs since last email sent for {url_slug}".format(url_slug=user.url_slug))
status = "no new diffs"
# set last email check date
db.session.merge(user)
user.last_email_check = now
try:
db.session.commit()
logger.info(u"updated user object in send_email_if_new_diffs for {url_slug}".format(url_slug=user.url_slug))
except InvalidRequestError:
logger.info(u"rollback, trying again to update user object in send_email_if_new_diffs for {url_slug}".format(url_slug=user.url_slug))
db.session.rollback()
db.session.commit()
logger.info(u"after rollback updated user object in send_email_if_new_diffs for {url_slug}".format(url_slug=user.url_slug))
return status
def send_email_report(user, now=None):
status = "started"
if not now:
now = datetime.datetime.utcnow()
template_filler_dict = notification_report.make(user)
db.session.merge(user)
if template_filler_dict["cards"]:
if os.getenv("ENVIRONMENT", "testing") == "production":
email = user.email
else:
email = "heather@impactstory.org"
user.last_email_sent = now
try:
db.session.commit()
logger.info(u"updated user object in send_email_report for {url_slug}".format(url_slug=user.url_slug))
except InvalidRequestError:
logger.info(u"rollback, trying again to update user object in send_email_report for {url_slug}".format(url_slug=user.url_slug))
db.session.rollback()
db.session.commit()
logger.info(u"after rollback updated user object in send_email_report for {url_slug}".format(url_slug=user.url_slug))
msg = emailer.send(email, "Your latest research impacts", "report", template_filler_dict)
status = "emailed"
logger.info(u"SENT EMAIL to {url_slug}!!".format(url_slug=user.url_slug))
else:
status = "not emailed, no cards made"
logger.info(u"not sending email, no cards made for {url_slug}".format(url_slug=user.url_slug))
return status
# @task(base=TaskThatSavesState)
# def update_from_linked_account(user, account):
# tiids = user.update_products_from_linked_account(account, update_even_removed_products=False)
# return tiids
# @task(base=TaskThatSavesState)
# def link_accounts_and_update(user):
# all_tiids = []
# for account in ["github", "slideshare", "figshare", "orcid"]:
# all_tiids += update_from_linked_account(user, account)
# all_tiids += user.refresh_products(source="scheduled")
# return all_tiids
# @task(base=TaskThatSavesState)
# def dedup_and_create_cards_and_email(user, override_with_send=True):
# deduplicate(user)
# # create_cards(user)
# send_email_report(user, override_with_send=True)
# @task(base=TaskThatSavesState)
# def create_cards(user):
# timestamp = datetime.datetime.utcnow()
# cards = []
# cards += ProductNewMetricCardGenerator.make(user, timestamp)
# cards += ProfileNewMetricCardGenerator.make(user, timestamp)
# for card in cards:
# db.session.add(card)
# try:
# db.session.commit()
# except InvalidRequestError:
# db.session.rollback()
# db.session.commit()
# return cards