def _updateProgress(self, job, total, current, message, notify, user, updates): """Helper for updating job progress information.""" state = JobStatus.toNotificationStatus(job['status']) if current is not None: current = float(current) if total is not None: total = float(total) if job['progress'] is None: if notify and job['userId']: notification = self._createProgressNotification( job, total, current, state, message) notificationId = notification['_id'] else: notificationId = None job['progress'] = { 'message': message, 'total': total, 'current': current, 'notificationId': notificationId } updates['$set']['progress'] = job['progress'] else: if total is not None: job['progress']['total'] = total updates['$set']['progress.total'] = total if current is not None: job['progress']['current'] = current updates['$set']['progress.current'] = current if message is not None: job['progress']['message'] = message updates['$set']['progress.message'] = message if notify and user: if job['progress']['notificationId'] is None: notification = self._createProgressNotification( job, total, current, state, message, user) nid = notification['_id'] job['progress']['notificationId'] = nid updates['$set']['progress.notificationId'] = nid else: notification = Notification().load( job['progress']['notificationId']) Notification().updateProgress( notification, state=state, message=job['progress']['message'], current=job['progress']['current'], total=job['progress']['total'])
def __init__(self, on, interval=0.5, **kwargs): self.on = on self.interval = interval if on: self._lastSave = time.time() self.progress = Notification().initProgress(**kwargs)
def _createUpdateStatusNotification(self, now, user, job): expires = now + datetime.timedelta(seconds=30) filtered = self.filter(job, user) filtered.pop('kwargs', None) filtered.pop('log', None) Notification().createNotification(type='job_status', data=filtered, user=user, expires=expires)
def _updateLog(self, job, log, overwrite, now, notify, user, updates): """Helper for updating a job's log.""" if overwrite: updates['$set']['log'] = [log] else: updates['$push']['log'] = log if notify and user: expires = now + datetime.timedelta(seconds=30) Notification().createNotification(type='job_log', data={ '_id': job['_id'], 'overwrite': overwrite, 'text': log }, user=user, expires=expires)
def _createProgressNotification(self, job, total, current, state, message, user=None): if not user: user = User().load(job['userId'], force=True) # TODO support channel-based notifications for jobs. For # right now we'll just go through the user. return Notification().initProgress(user, job['title'], total, state=state, current=current, message=message, estimateTime=False, resource=job, resourceName=self.name)
def update(self, force=False, **kwargs): """ Update the underlying progress record. This will only actually save to the database if at least self.interval seconds have passed since the last time the record was written to the database. Accepts the same kwargs as Notification.updateProgress. :param force: Whether we should force the write to the database. Use only in cases where progress may be indeterminate for a long time. :type force: bool """ # Extend the response timeout, even if we aren't reporting the progress setResponseTimeLimit() if not self.on: return save = (time.time() - self._lastSave > self.interval) or force self.progress = Notification().updateProgress(self.progress, save, **kwargs) if save: self._lastSave = time.time()
def __exit__(self, excType, excValue, traceback): """ Once the context is exited, the progress is marked for deletion 30 seconds in the future, which should give all listeners time to poll and receive the final state of the progress record before it is deleted. """ if not self.on: return if excType is None and excValue is None: state = ProgressState.SUCCESS message = 'Done' else: state = ProgressState.ERROR message = 'Error' if isinstance(excValue, (ValidationException, RestException)): message = 'Error: ' + str(excValue) Notification().updateProgress(self.progress, state=state, message=message, expires=datetime.datetime.utcnow() + datetime.timedelta(seconds=30))
def createJob(self, title, type, args=(), kwargs=None, user=None, when=None, interval=0, public=False, handler=None, asynchronous=False, save=True, parentJob=None, otherFields=None): """ Create a new job record. :param title: The title of the job. :type title: str :param type: The type of the job. :type type: str :param args: Positional args of the job payload. :type args: list or tuple :param kwargs: Keyword arguments of the job payload. :type kwargs: dict :param user: The user creating the job. :type user: dict or None :param when: Minimum start time for the job (UTC). :type when: datetime :param interval: If this job should be recurring, set this to a value in seconds representing how often it should occur. Set to <= 0 for jobs that should only be run once. :type interval: int :param public: Public read access flag. :type public: bool :param handler: If this job should be handled by a specific handler, use this field to store that information. :param externalToken: If an external token was created for updating this job, pass it in and it will have the job-specific scope set. :type externalToken: token (dict) or None. :param asynchronous: Whether the job is to be run asynchronously. For now this only applies to jobs that are scheduled to run locally. :type asynchronous: bool :param save: Whether the documented should be saved to the database. :type save: bool :param parentJob: The job which will be set as a parent :type parentJob: Job :param otherFields: Any additional fields to set on the job. :type otherFields: dict """ now = datetime.datetime.utcnow() if when is None: when = now if kwargs is None: kwargs = {} otherFields = otherFields or {} parentId = None if parentJob: parentId = parentJob['_id'] job = { 'title': title, 'type': type, 'args': args, 'kwargs': kwargs, 'created': now, 'updated': now, 'when': when, 'interval': interval, 'status': JobStatus.INACTIVE, 'progress': None, 'log': [], 'meta': {}, 'handler': handler, 'asynchronous': asynchronous, 'timestamps': [], 'parentId': parentId } job.update(otherFields) self.setPublic(job, public=public) if user: job['userId'] = user['_id'] self.setUserAccess(job, user=user, level=AccessType.ADMIN) else: job['userId'] = None if save: job = self.save(job) if user: deserialized_kwargs = job['kwargs'] job['kwargs'] = json_util.dumps(job['kwargs']) Notification().createNotification( type='job_created', data=job, user=user, expires=datetime.datetime.utcnow() + datetime.timedelta(seconds=30)) job['kwargs'] = deserialized_kwargs return job