django-restit 4.2.62__py3-none-any.whl → 4.2.63__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
account/fcm/google.py ADDED
@@ -0,0 +1,53 @@
1
+ import json
2
+ import requests
3
+ import time
4
+ from jwt import JWT, jwk_from_dict
5
+ from datetime import datetime, timedelta
6
+
7
+
8
+ def oauthLogin(service_account_info):
9
+ # Token endpoint
10
+ token_uri = service_account_info["token_uri"]
11
+
12
+ # The current time and expiration time for the assertion
13
+ issued_at_time = datetime.utcnow()
14
+ expiration_time = issued_at_time + timedelta(minutes=60)
15
+
16
+ # JWT Header
17
+ jwt_header = {
18
+ "alg": "RS256",
19
+ "typ": "JWT",
20
+ "kid": service_account_info["private_key_id"]
21
+ }
22
+
23
+ # JWT Payload
24
+ jwt_payload = {
25
+ "iss": service_account_info["client_email"],
26
+ "sub": service_account_info["client_email"],
27
+ "aud": token_uri,
28
+ "iat": int(issued_at_time.timestamp()),
29
+ "exp": int(expiration_time.timestamp()),
30
+ "scope": "https://www.googleapis.com/auth/firebase.messaging"
31
+ }
32
+
33
+ # Create a JWT
34
+ jwt_instance = JWT()
35
+ private_key = jwk_from_dict({"k": service_account_info["private_key"], "kty": "RSA"})
36
+ assertion = jwt_instance.encode(jwt_header, jwt_payload, private_key)
37
+
38
+ # Exchange the JWT for an access token
39
+ response = requests.post(token_uri, data={
40
+ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
41
+ "assertion": assertion
42
+ })
43
+
44
+ response_data = response.json()
45
+
46
+ access_token = response_data.get("access_token")
47
+ expires_in = response_data.get("expires_in") # Seconds until the token expires
48
+
49
+ # You can now use this access_token to authenticate requests to Google APIs.
50
+ # Remember to refresh the token using a similar process once it's close to expiration.
51
+
52
+ print("Access Token:", access_token)
53
+ print("Expires In:", expires_in)
account/fcm/v1.py ADDED
@@ -0,0 +1,47 @@
1
+ from google.oauth2 import service_account
2
+ import google.auth.transport.requests
3
+ import requests
4
+ from rest import settings
5
+ from rest import log
6
+
7
+
8
+ FCM_ENDPOINT = 'https://fcm.googleapis.com/v1/projects/{}/messages:send'
9
+
10
+ logger = log.getLogger("fcm", filename="fcm.log")
11
+
12
+
13
+ def getCredentials(data):
14
+ # Load the credentials from the dictionary
15
+ credentials = service_account.Credentials.from_service_account_info(
16
+ data,
17
+ scopes=["https://www.googleapis.com/auth/firebase.messaging"])
18
+ # Use the credentials to authenticate a Requests session
19
+ auth_req = google.auth.transport.requests.Request()
20
+ credentials.refresh(auth_req)
21
+ return credentials
22
+
23
+
24
+ def sendToDevice(device, data):
25
+ return sendData(device.cm_token, data)
26
+
27
+
28
+ def sendNotification(to_token, title, body):
29
+ return postMessage(dict(token=to_token, notification=dict(title=title, body=body)))
30
+
31
+
32
+ def sendData(to_token, data, priority="high"):
33
+ return postMessage(dict(token=to_token, data=data, content_available=True, priority=priority))
34
+
35
+
36
+ def postMessage(credentials, payload):
37
+ logger.info("sending FCM", payload)
38
+ headers = {
39
+ 'Authorization': 'Bearer ' + credentials.token,
40
+ 'Content-Type': 'application/json; UTF-8',
41
+ }
42
+ body = dict(message=payload)
43
+ resp = requests.post(FCM_ENDPOINT.format(credentials.project_id), headers=headers, json=body)
44
+ logger.info("response", resp.text)
45
+ return resp
46
+
47
+
account/models/device.py CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  from django.db import models
3
3
  from rest import models as rm
4
+ from rest import fields as rf
4
5
  from rest import settings
5
6
  from objict import objict
6
7
  from datetime import datetime
@@ -110,7 +111,36 @@ class MemberDeviceMetaData(rm.MetaDataBase):
110
111
  parent = models.ForeignKey(MemberDevice, related_name="properties", on_delete=models.CASCADE)
111
112
 
112
113
 
114
+ class CloudCredentials(models.Model, rm.RestModel, rm.MetaDataModel):
115
+ """
116
+ MemberDevice Model tracks personal devices associated with a user.
117
+ This can include mobile and desktop devices.
118
+ """
119
+ created = models.DateTimeField(auto_now_add=True)
120
+ modified = models.DateTimeField(auto_now=True)
121
+ group = models.ForeignKey("account.Group", related_name="cloud_credentials", on_delete=models.CASCADE)
122
+
123
+ name = models.CharField(max_length=128, blank=True, null=True, default=None)
124
+ uuid = models.CharField(db_index=True, max_length=64, blank=True, null=True, default=None)
125
+ state = models.IntegerField(db_index=True, default=1)
126
+
127
+ credentials = rf.JSONField()
128
+
129
+ def sendToDevice(self, device, message):
130
+ pass
131
+
132
+ def sendNotification(self, token, title, body):
133
+ pass
134
+
135
+
136
+ class CloudCredentialsMetaData(rm.MetaDataBase):
137
+ parent = models.ForeignKey(CloudCredentials, related_name="properties", on_delete=models.CASCADE)
138
+
139
+
113
140
  def getCloudMessanger(name):
141
+ creds = CloudCredentials.objects.filter(uuid=name).last()
142
+ if creds is not None:
143
+ return creds
114
144
  if name not in CM_BACKENDS:
115
145
  if name == "fcm":
116
146
  from account import fcm
account/models/feeds.py CHANGED
@@ -39,9 +39,9 @@ class GroupFeed(FeedBase, rm.RestModel):
39
39
  VIEW_PERMS = ["manage_groups", "manage_group", "view_all_groups"]
40
40
  GRAPHS = {
41
41
  "default": {
42
- "recurse_into": ["generic__component"],
43
42
  "graphs": {
44
- "member": "basic"
43
+ "member": "basic",
44
+ "generic__component": "basic"
45
45
  }
46
46
  }
47
47
  }
auditlog/cloudwatch.py ADDED
@@ -0,0 +1,93 @@
1
+ import boto3
2
+ import datetime
3
+ import time
4
+ import json
5
+ from objict import objict
6
+ from rest import settings
7
+ from concurrent.futures import ThreadPoolExecutor
8
+
9
+
10
+ LOG_CACHE = objict()
11
+
12
+
13
+ def getClient():
14
+ if LOG_CACHE.client is None:
15
+ key = settings.AWS_KEY
16
+ secret = settings.AWS_SECRET
17
+ region = settings.AWS_REGION
18
+ LOG_CACHE.client = boto3.client("logs", aws_access_key_id=key, aws_secret_access_key=secret, region_name=region)
19
+ return LOG_CACHE.client
20
+
21
+
22
+ def log(data, log_group, log_stream):
23
+ if LOG_CACHE.pool is None:
24
+ LOG_CACHE.pool = ThreadPoolExecutor(max_workers=1)
25
+ LOG_CACHE.pool.submit(logToCloudWatch, data, log_group, log_stream)
26
+ return True
27
+
28
+
29
+ def logToCloudWatch(data, log_group, log_stream):
30
+ message = data
31
+ if isinstance(message, dict):
32
+ message = json.dumps(message)
33
+ return logBatchToCloudWatch([
34
+ dict(
35
+ timestamp=int(datetime.datetime.utcnow().timestamp() * 1000),
36
+ message=message)
37
+ ], log_group, log_stream)
38
+
39
+
40
+ def logBatchToCloudWatch(batch, log_group, log_stream):
41
+ return getClient().put_log_events(
42
+ logGroupName=log_group,
43
+ logStreamName=log_stream,
44
+ logEvents=batch
45
+ )
46
+
47
+
48
+ def getLogGroups():
49
+ response = getClient().describe_log_groups()
50
+ return response.get('logGroups', [])
51
+
52
+
53
+ def createLogStream(log_group, log_stream):
54
+ try:
55
+ getClient().create_log_stream(logGroupName=log_group, logStreamName=log_stream)
56
+ except Exception:
57
+ pass # Log stream already exists, no need to create it
58
+
59
+
60
+ def getInsights(log_group, start_time, end_time, query_string):
61
+ """
62
+ Executes a CloudWatch Logs Insights query and returns the results.
63
+
64
+ :param log_group: The name of the log group to query.
65
+ :param start_time: The start time of the query (epoch time in seconds).
66
+ :param end_time: The end time of the query (epoch time in seconds).
67
+ :param query_string: The query string to use.
68
+ :param region_name: AWS region name.
69
+ :return: The query results.
70
+ """
71
+ # Create a CloudWatch Logs client
72
+ client = getClient()
73
+
74
+ # Start the query
75
+ start_query_response = client.start_query(
76
+ logGroupName=log_group,
77
+ startTime=start_time,
78
+ endTime=end_time,
79
+ queryString=query_string,
80
+ )
81
+
82
+ query_id = start_query_response['queryId']
83
+
84
+ # Wait for the query to complete
85
+ response = None
86
+ while response is None or response['status'] == 'Running':
87
+ time.sleep(1) # Sleep to rate limit the polling
88
+ response = client.get_query_results(queryId=query_id)
89
+
90
+ if response['status'] == 'Complete':
91
+ return response['results']
92
+ else:
93
+ raise Exception(f"Query did not complete successfully. Status: {response['status']}")
auditlog/models.py CHANGED
@@ -8,6 +8,7 @@ import copy
8
8
  from rest.models import RestModel
9
9
  from rest import helpers
10
10
  from rest.log import getLogger
11
+ from auditlog import cloudwatch
11
12
 
12
13
  import traceback
13
14
 
@@ -281,7 +282,9 @@ class PersistentLog(models.Model, RestModel):
281
282
 
282
283
  @staticmethod
283
284
  def log(message, level=0, request=None, component=None,
284
- pkey=None, action=None, group=None, path=None, method=None, tid=None, no_truncate=False):
285
+ pkey=None, action=None, group=None, path=None,
286
+ method=None, tid=None, no_truncate=False,
287
+ aws_log_group=None, aws_log_stream=None):
285
288
  plog = PersistentLog.createLogFromRequest(
286
289
  request, component=component, tid=tid, pkey=pkey, action=action, group=group,
287
290
  path=path, method=method, level=level)
@@ -290,6 +293,8 @@ class PersistentLog(models.Model, RestModel):
290
293
 
291
294
  try:
292
295
  plog.save()
296
+ if aws_log_group and aws_log_stream:
297
+ cloudwatch.log(plog.toDict(), aws_log_group, aws_log_stream)
293
298
  except Exception:
294
299
  helpers.log_exception(plog.message)
295
300
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-restit
3
- Version: 4.2.62
3
+ Version: 4.2.63
4
4
  Summary: A Rest Framework for DJANGO
5
5
  License: MIT
6
6
  Author: Ian Starnes
@@ -1,6 +1,8 @@
1
1
  account/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  account/admin.py,sha256=8MQ1gAgjpPUC_SGCVMwd7I8fqLetqcutLiasjssEPRY,1839
3
3
  account/fcm/__init__.py,sha256=gSTChf99a5T20oKuf4Lh9UR-f3V5qI20AZxIhlPlnVs,980
4
+ account/fcm/google.py,sha256=T-TZZgrT90YY9sEtKC6rNGpwfBmVTCuFpB31vRDguu0,1710
5
+ account/fcm/v1.py,sha256=PGpSmAnq40cMao4SwDU2tv6m5PpteDWLbzvldjalRJ8,1434
4
6
  account/migrations/0001_initial.py,sha256=PhYNDTiwjyUplErBmYc34ecynLIEJL2JuC02o8GCXes,15894
5
7
  account/migrations/0003_member_phone_number.py,sha256=auAJCfxsK-y3Veo0vwZfrIZxvwHYBjg5CpYRgCWghCQ,738
6
8
  account/migrations/0004_group_modified_alter_group_created.py,sha256=20iNFlUGtRCWUsxEqTJFRQKQ73z4REhfo-hGam8IHXY,551
@@ -21,8 +23,8 @@ account/migrations/0018_userpasskey.py,sha256=SdXYo4TkIeP5wLNfCza3Jq5-gKuUufzTHG
21
23
  account/migrations/0019_group_location.py,sha256=EfMB_w4qWUGDqQeNc453PFZwpjpTeoA6xr6Qgo_YAOM,601
22
24
  account/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
25
  account/models/__init__.py,sha256=cV_lMnT2vL_mjiYtT4hlcIHo52ocFbGSNVkOIHHLXZY,385
24
- account/models/device.py,sha256=EzkEPjpJxxt7OGDfEkRXIpoutdfSSPBGquVdKF6nJD0,4114
25
- account/models/feeds.py,sha256=JYBfAmvSnbFaFV90Di2Hu07-SLAhyBMw0pCOvJ_eXqs,2016
26
+ account/models/device.py,sha256=9wq41JuP5hvDkGauZCDHF7hbRWsIAHwe1kP3uEQ0QI8,5220
27
+ account/models/feeds.py,sha256=vI7fG4ASY1M0Zjke24RdnfDcuWeATl_yR_25jPmT64g,2011
26
28
  account/models/group.py,sha256=4fk-RavG1wNVzfuXMaD2XDiX2DhK1hgL0-OA54Uym18,21576
27
29
  account/models/legacy.py,sha256=zYdtv4LC0ooxPVqWM-uToPwV-lYWQLorSE6p6yn1xDw,2720
28
30
  account/models/member.py,sha256=CxxhNnFCQPEw7MGc5147J8eandYqUj6HhhQ_YRyFQX4,50397
@@ -57,11 +59,12 @@ account/templates/email/simple/reset_code.html,sha256=Dln4C8jC-PI1ToS-k2VpRUjXya
57
59
  auditlog/README,sha256=q4DXhdz5CuMyuxYISHXzhlHnIkRJlojwOMchLzW2qOI,520
58
60
  auditlog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
61
  auditlog/admin.py,sha256=-q7fstdFjNeDFfbwdrxVqy0WGKxMpBwrsM7AyG1p80g,1006
62
+ auditlog/cloudwatch.py,sha256=R-B_ByVM3We26YnDoFYIQeWV31CUyS63QTojRAkfWa8,2805
60
63
  auditlog/decorators.py,sha256=ZoIv0fhZjxtMEV15NcKijW4xPF5UEScPna60zB3TxZo,6553
61
64
  auditlog/middleware.py,sha256=Q4bXg8rnm8y2fMnAsN6ha3Fz6TW8jIzLnvpu4H9SpWE,1537
62
65
  auditlog/migrations/0001_initial.py,sha256=X171gKQZIaTO9FGNG1yKTjGSZS0ZjZj5gvimF9-_kks,3309
63
66
  auditlog/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
- auditlog/models.py,sha256=6CyWMRNvqCYoyiXE9_qk41EJFjutwo6nfoP9KKYWiYw,16294
67
+ auditlog/models.py,sha256=skDAiuzR4chC-WNIaH2nm_VVcbnDD6ZtUxBwhk7UY8U,16517
65
68
  auditlog/periodic.py,sha256=AUhDeVsZtC47BJ-lklvYEegHoxAzj1RpIvRFSsM7g5E,363
66
69
  auditlog/rpc.py,sha256=lhme-ScqwVSKfHo3RlzMVm_C-Yx4xOxPogBEv7b3w1M,1720
67
70
  auditlog/tq.py,sha256=OgzJVspWI6FL92GEhDPtabYoP_Hd3zGNh0E297abz3Y,2415
@@ -364,7 +367,7 @@ pushit/utils.py,sha256=IeTCGa-164nmB1jIsK1lu1O1QzUhS3BKfuXHGjCW-ck,2121
364
367
  rest/.gitignore,sha256=TbEvWRMnAiajCTOdhiNrd9eeCAaIjRp9PRjE_VkMM5g,118
365
368
  rest/README.md,sha256=V3ETc-cJu8PZIbKr9xSe_pA4JEUpC8Dhw4bQeVCDJPw,5460
366
369
  rest/RemoteEvents.py,sha256=nL46U7AuxIrlw2JunphR1tsXyqi-ep_gD9CYGpYbNgE,72
367
- rest/__init__.py,sha256=nqUQGTs1aNMT8VVPk-Lu2qFRYD6XdwBeqh9cbJmiwxY,121
370
+ rest/__init__.py,sha256=bERP1K_f9PlA9V_pCBU1-PcbHQxiWC19_283DRHe10o,121
368
371
  rest/arc4.py,sha256=y644IbF1ec--e4cUJ3KEYsewTCITK0gmlwa5mJruFC0,1967
369
372
  rest/cache.py,sha256=1Qg0rkaCJCaVP0-l5hZg2CIblTdeBSlj_0fP6vlKUpU,83
370
373
  rest/crypto/__init__.py,sha256=Tl0U11rgj1eBYqd6OXJ2_XSdNLumW_JkBZnaJqI6Ldw,72
@@ -445,17 +448,17 @@ taskqueue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
445
448
  taskqueue/admin.py,sha256=E6zXoToS_ea3MdoGjZzF1JiepWFtDSoZUQdan8H-pXI,208
446
449
  taskqueue/migrations/0001_initial.py,sha256=JwYib8CK5ftSXlfxKZUcKEEVsXktNB5q3h-2tu9inGk,4738
447
450
  taskqueue/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
448
- taskqueue/models.py,sha256=tCFAKSr8JybUJV4gijg3wUCwQMJiHUsRBgHH2CWonfY,22119
451
+ taskqueue/models.py,sha256=N3_9jWHPGzs6UxlAzpjst6qPhvIs1RELTy1cMXMeXsA,22407
449
452
  taskqueue/periodic.py,sha256=2i0271khrCow3hDmlNEcoAZnesBVl40jd7MIim2Cxs4,3543
450
453
  taskqueue/rpc.py,sha256=If5E9D9AR2RqW4lHRaDuD9L9b9ZfL_PaBQ6iX91ehvU,5736
451
454
  taskqueue/tq.py,sha256=PzSoDrawYcqZylruEgsK95gcJ4J_VhdM6rxg9V6_X8E,942
452
455
  taskqueue/transports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
453
456
  taskqueue/transports/email.py,sha256=H4GaomiyCck5R_AOcsrMawCl-_Bp_Zg-uWto9t1Xcoo,623
454
- taskqueue/transports/http.py,sha256=kRBjHiwTmk5b4TMdZWt1EKKcjfSlX8DFmtmJaRGZQFw,681
457
+ taskqueue/transports/http.py,sha256=AzliUnw_LuyO2zZZOoUAJGFcTV-Gxt1iE3hCVnIiyGQ,839
455
458
  taskqueue/transports/s3.py,sha256=fMosL893u1iQdo6Y1djwb7KEoNo6TTsDPJl13OJdJP8,1913
456
459
  taskqueue/transports/sftp.py,sha256=jT1_krjTHA7DCAukD85aGYRCg9m0cEH9EWzOC-wJGdk,1891
457
460
  taskqueue/transports/sms.py,sha256=H1-LIGEMfbUNqJD9amRcsvKUSwtz9yBj1QNfB7EHjHE,142
458
- taskqueue/worker.py,sha256=u7H-FLkz7vpBoDaab9hpw4RduvG1ZTdtlw3PSHRBmUw,15737
461
+ taskqueue/worker.py,sha256=wzp44fk6LX94MdrXLp_IJmWgutLCBKqoobk433OiLqw,15822
459
462
  telephony/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
460
463
  telephony/admin.py,sha256=iOdsBfFFbBisdqKSZ36bIrh_z5sU0Wx_PkaFi8wd1iA,243
461
464
  telephony/decorators.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -497,7 +500,7 @@ ws4redis/servers/uwsgi.py,sha256=VyhoCI1DnVFqBiJYHoxqn5Idlf6uJPHvfBKgkjs34mo,172
497
500
  ws4redis/settings.py,sha256=K0yBiLUuY81iDM4Yr-k8hbvjn5VVHu5zQhmMK8Dtz0s,1536
498
501
  ws4redis/utf8validator.py,sha256=S0OlfjeGRP75aO6CzZsF4oTjRQAgR17OWE9rgZdMBZA,5122
499
502
  ws4redis/websocket.py,sha256=R0TUyPsoVRD7Y_oU7w2I6NL4fPwiz5Vl94-fUkZgLHA,14848
500
- django_restit-4.2.62.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
501
- django_restit-4.2.62.dist-info/METADATA,sha256=XPJeA3gzUze_WPJT54LMV1INVHI_ru-82Nrsu82Wc30,7594
502
- django_restit-4.2.62.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
503
- django_restit-4.2.62.dist-info/RECORD,,
503
+ django_restit-4.2.63.dist-info/LICENSE.md,sha256=VHN4hhEeVOoFjtG-5fVv4jesA4SWi0Z-KgOzzN6a1ps,1068
504
+ django_restit-4.2.63.dist-info/METADATA,sha256=152iRXbYrfis4DIYRd86mMH6UeHPVTtKD8H2B0RgMD0,7594
505
+ django_restit-4.2.63.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
506
+ django_restit-4.2.63.dist-info/RECORD,,
rest/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  from .uberdict import UberDict # noqa: F401
2
2
  from .settings_helper import settings # noqa: F401
3
3
 
4
- __version__ = "4.2.62"
4
+ __version__ = "4.2.63"
taskqueue/models.py CHANGED
@@ -226,23 +226,25 @@ class Task(models.Model, RestModel):
226
226
  self.state = TASK_STATE_COMPLETED
227
227
  self.save()
228
228
 
229
- def failed(self, reason=None):
229
+ def failed(self, reason=None, category="taskqueue_errors"):
230
230
  if reason and len(reason) > 250:
231
231
  reason = reason[:250]
232
232
  self.reason = reason
233
233
  self.state = TASK_STATE_FAILED
234
- self.notifyError()
234
+ self.notifyError(category=category, reason=reason)
235
235
  self.save()
236
236
 
237
- def notifyError(self):
238
- handler = f"{self.model}{self.fname}"
237
+ def notifyError(self, category="taskqueue_errors", reason=None):
238
+ handler = f"{self.model}.{self.fname}"
239
239
  subject = f"TaskQueue - {handler}"
240
+ if reason is None:
241
+ reason = self.reason
240
242
 
241
- msg = f"{handler}<br>\n{self.reason}"
243
+ msg = f"{handler}<br>\n{reason}"
242
244
  metadata = {
243
245
  "server": settings.get("HOSTNAME", "unknown"),
244
246
  "task": self.pk,
245
- "reason": self.reason,
247
+ "reason": reason,
246
248
  "app": self.model,
247
249
  "fname": self.fname,
248
250
  "channel": self.channel,
@@ -253,6 +255,9 @@ class Task(models.Model, RestModel):
253
255
  if self.data:
254
256
  if self.data.url:
255
257
  metadata["url"] = self.data.url
258
+ from urllib.parse import urlparse
259
+ purl = urlparse(self.data.url)
260
+ metadata["host"] = purl.netloc
256
261
  msg = f"{msg}<br>\n{self.data.url}"
257
262
  if self.data.log_component:
258
263
  metadata["component"] = self.data.log_component
@@ -261,7 +266,7 @@ class Task(models.Model, RestModel):
261
266
  try:
262
267
  import incident
263
268
  incident.event_now(
264
- "taskqueue_errors", description=subject, details=msg,
269
+ category, description=subject, details=msg,
265
270
  level=3, metadata=metadata)
266
271
  except Exception as err:
267
272
  self.log(str(err), kind="error")
@@ -15,9 +15,13 @@ def REQUEST(task):
15
15
  data=task.data.data, raw_response=True,
16
16
  verify=False, timeout=REQ_TIMEOUT)
17
17
  task.log(resp.text, kind="response")
18
- return resp.status_code == 200
18
+ if resp.status_code == 200:
19
+ return True
20
+ task.reason = f"invalid response of {resp.status_code}"
19
21
  except requests.Timeout:
22
+ task.reason = "request timed out"
20
23
  task.log("request timed out", kind="error")
21
24
  except Exception as err:
25
+ task.reason = str(err)
22
26
  task.log_exception(err)
23
27
  return False
taskqueue/worker.py CHANGED
@@ -198,6 +198,8 @@ class WorkManager(object):
198
198
  task.completed()
199
199
  elif task.attempts < task.max_attempts:
200
200
  # -1 will auto calculate retry with back off
201
+ # lets report the issue
202
+ task.notifyError(reason=task.reason)
201
203
  task.retry_later(from_now_secs=-1)
202
204
  else:
203
205
  task.failed("max attempts")