Source code for pyams_utils.progress

#
# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#

"""PyAMS_utils.progress module

This module can be used to get progress status on a long running operation.

The process is a s follow:

 - the client generate a "progress ID"; this ID can be any unique ID, and can be generated by
   MyAMS client library
 - the client browser send a POST request containg this progress ID to a view
 - the view calls "init_progress_status(progress_id, request.principal.id, "Task label") when
   starting it's long operation
 - during the operation, a call is made regularly to "set_progress_status(progress_id)"; additional
   arguments can contain a status (to indicate if operation is finished or not), and a simple
   "message" or two "length" and "current" arguments which can specify the length of the operation
   and it's current position
 - at the end of the operation, the view calls "set_progress_status(progress_id, 'finished')" to
   specify that the operation is finished.

During the whole operation, while waiting for server response, the client browser can send
requests to "get_progress_status.json", providing the progress ID, to get the operation progress.
This last operation is done automatically in PyAMS forms.
"""

from datetime import datetime
from threading import local

from beaker import cache
from pyramid.httpexceptions import HTTPBadRequest
from pyramid.view import view_config

from pyams_utils.lock import locked


__docformat__ = 'restructuredtext'


_LOCAL = local()


PROGRESS_CACHE_NAME = 'PyAMS::progress'
PROGRESS_TASKS_CACHE_KEY = 'PyAMS::progress::running_tasks'
PROGRESS_LOCK_NAME = 'tasks_progress_lock'
PROGRESS_TASK_KEY = 'PyAMS::progress::task::{0}'


[docs]def get_tasks_cache(): """Get cache storing tasks list""" try: tasks_cache = _LOCAL.running_tasks_cache except AttributeError: manager = cache.CacheManager(**cache.cache_regions['persistent']) tasks_cache = _LOCAL.running_tasks_cache = manager.get_cache(PROGRESS_CACHE_NAME) return tasks_cache
[docs]def get_progress_cache(): """Get cache storing tasks progress""" try: local_cache = _LOCAL.progress_cache except AttributeError: manager = cache.CacheManager(**cache.cache_regions['default']) local_cache = _LOCAL.progress_cache = manager.get_cache(PROGRESS_CACHE_NAME) return local_cache
[docs]def get_running_tasks(): """Get list of running tasks""" tasks_cache = get_tasks_cache() return tasks_cache.get_value(PROGRESS_TASKS_CACHE_KEY, createfunc=set)
[docs]def set_running_tasks(tasks): """Update list of running tasks""" tasks_cache = get_tasks_cache() tasks_cache.set_value(PROGRESS_TASKS_CACHE_KEY, tasks)
@locked(name=PROGRESS_LOCK_NAME) def init_progress_status(progress_id, owner, label, tags=None, length=None, current=None): # pylint: disable=too-many-arguments """Initialize progress status for given task :param str progress_id: task ID :param str owner: user ID associated with this task :param str label: label associated with this task :param tags: list of tags associated with given task :param int length: whole length of the given task, if available :param int current: current position in the whole task length, if available """ status = { 'status': 'running', 'owner': owner, 'label': label, 'tags': tags, 'length': length, 'current': current, 'started': datetime.utcnow().isoformat() } # Store task status cache_key = PROGRESS_TASK_KEY.format(progress_id) progress_cache = get_progress_cache() progress_cache.set_value(cache_key, status) # Store task in running tasks list tasks = get_running_tasks() tasks.add(progress_id) set_running_tasks(tasks) @locked(name=PROGRESS_LOCK_NAME) def get_progress_status(progress_id): """Get status of given task""" progress_cache = get_progress_cache() cache_key = PROGRESS_TASK_KEY.format(progress_id) try: status = progress_cache.get_value(cache_key) except KeyError: status = {'status': 'unknown'} else: if status.get('status') == 'finished': progress_cache.remove_value(cache_key) tasks = get_running_tasks() if progress_id in tasks: tasks.remove(progress_id) return status @locked(name=PROGRESS_LOCK_NAME) def set_progress_status(progress_id, status='running', message=None, length=None, current=None): """Set status of given task :param str progress_id: task ID :param str status: new status of the given task :param str message: status message associated with the given task :param int length: whole length of the given task, if available :param int current: current position in the whole task length, if available """ progress_cache = get_progress_cache() cache_key = PROGRESS_TASK_KEY.format(progress_id) try: task_status = progress_cache.get_value(cache_key) except KeyError: task_status = {'status': 'unknown'} task_status.update({ 'status': status, 'message': message, 'length': length, 'current': current }) progress_cache.set_value(cache_key, task_status)
[docs]@view_config(name='get-progress-status.json', renderer='json', xhr=True) def get_progress_status_view(request): """Get progress status of a given task Each submitted task is identified by an ID defined when the task is created """ if 'progress_id' not in request.params: raise HTTPBadRequest("Missing argument") return get_progress_status(request.params['progress_id'])