././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1625483600.9365168 celery-progress-0.1.1/ 0000775 0001750 0001750 00000000000 00000000000 014577 5 ustar 00czue czue 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0 celery-progress-0.1.1/LICENSE 0000664 0001750 0001750 00000002051 00000000000 015602 0 ustar 00czue czue 0000000 0000000 MIT License Copyright (c) 2018 Cory Zue Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0 celery-progress-0.1.1/MANIFEST.in 0000664 0001750 0001750 00000000146 00000000000 016336 0 ustar 00czue czue 0000000 0000000 include LICENSE include README.md recursive-include celery_progress/static * recursive-include docs * ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1625483600.9365168 celery-progress-0.1.1/PKG-INFO 0000664 0001750 0001750 00000020115 00000000000 015673 0 ustar 00czue czue 0000000 0000000 Metadata-Version: 2.1 Name: celery-progress Version: 0.1.1 Summary: Drop in, configurable, dependency-free progress bars for your Django/Celery applications. Home-page: https://github.com/czue/celery-progress Author: Cory Zue Author-email: cory@coryzue.com License: MIT License Platform: UNKNOWN Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Framework :: Django :: 1.11 Classifier: Framework :: Django :: 2.0 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Description-Content-Type: text/markdown Provides-Extra: websockets Provides-Extra: redis Provides-Extra: rabbitmq License-File: LICENSE # Celery Progress Bars for Django Drop in, dependency-free progress bars for your Django/Celery applications. Super simple setup. Lots of customization available. ## Demo [Celery Progress Bar demo on Build With Django](https://buildwithdjango.com/projects/celery-progress/) ### Github demo application: build a download progress bar for Django Starting with Celery can be challenging, [eeintech](https://github.com/eeintech) built a complete [Django demo application](https://github.com/eeintech/django-celery-progress-demo) along with a [step-by-step guide](https://eeinte.ch/stream/progress-bar-django-using-celery/) to get you started on building your own progress bar! ## Installation If you haven't already, make sure you have properly [set up celery in your project](https://docs.celeryproject.org/en/stable/getting-started/first-steps-with-celery.html#first-steps). Then install this library: ```bash pip install celery-progress ``` ## Usage ### Prerequisites First add `celery_progress` to your `INSTALLED_APPS` in `settings.py`. Then add the following url config to your main `urls.py`: ```python from django.urls import re_path, include re_path(r'^celery-progress/', include('celery_progress.urls')), # the endpoint is configurable ``` ### Recording Progress In your task you should add something like this: ```python from celery import shared_task from celery_progress.backend import ProgressRecorder import time @shared_task(bind=True) def my_task(self, seconds): progress_recorder = ProgressRecorder(self) result = 0 for i in range(seconds): time.sleep(1) result += i progress_recorder.set_progress(i + 1, seconds) return result ``` You can add an optional progress description like this: ```python progress_recorder.set_progress(i + 1, seconds, description='my progress description') ``` ### Displaying progress In the view where you call the task you need to get the task ID like so: **views.py** ```python def progress_view(request): result = my_task.delay(10) return render(request, 'display_progress.html', context={'task_id': result.task_id}) ``` Then in the page you want to show the progress bar you just do the following. #### Add the following HTML wherever you want your progress bar to appear: **display_progress.html** ```html
').text('Sum of all seconds is ' + result) ); } $(function () { CeleryProgressBar.initProgressBar(progressUrl, { onResult: customResult, }) }); ``` ## Customization The `initProgressBar` function takes an optional object of options. The following options are supported: | Option | What it does | Default Value | |--------|--------------|---------------| | pollInterval | How frequently to poll for progress (in milliseconds) | 500 | | progressBarId | Override the ID used for the progress bar | 'progress-bar' | | progressBarMessageId | Override the ID used for the progress bar message | 'progress-bar-message' | | progressBarElement | Override the *element* used for the progress bar. If specified, progressBarId will be ignored. | document.getElementById(progressBarId) | | progressBarMessageElement | Override the *element* used for the progress bar message. If specified, progressBarMessageId will be ignored. | document.getElementById(progressBarMessageId) | | resultElementId | Override the ID used for the result | 'celery-result' | | resultElement | Override the *element* used for the result. If specified, resultElementId will be ignored. | document.getElementById(resultElementId) | | onProgress | function to call when progress is updated | onProgressDefault | | onSuccess | function to call when progress successfully completes | onSuccessDefault | | onError | function to call on a known error with no specified handler | onErrorDefault | | onRetry | function to call when a task attempts to retry | onRetryDefault | | onIgnored | function to call when a task result is ignored | onIgnoredDefault | | onTaskError | function to call when progress completes with an error | onError | | onNetworkError | function to call on a network error (ignored by WebSocket) | onError | | onHttpError | function to call on a non-200 response (ignored by WebSocket) | onError | | onDataError | function to call on a response that's not JSON or has invalid schema due to a programming error | onError | | onResult | function to call when returned non empty result | CeleryProgressBar.onResultDefault | | barColors | dictionary containing color values for various progress bar states. Colors that are not specified will defer to defaults | barColorsDefault | The `barColors` option allows you to customize the color of each progress bar state by passing a dictionary of key-value pairs of `state: #hexcode`. The defaults are shown below. | State | Hex Code | Image Color | |-------|----------|:-------------:| | success | #76ce60 |  | | error | #dc4f63 |  | | progress | #68a9ef |  | | ignored | #7a7a7a |  | # WebSocket Support Additionally, this library offers WebSocket support using [Django Channels](https://channels.readthedocs.io/en/latest/) courtesy of [EJH2](https://github.com/EJH2/). A working example project leveraging WebSockets is [available here](https://github.com/EJH2/cp_ws-example). To use WebSockets, install with `pip install celery-progress[websockets,redis]` or `pip install celery-progress[websockets,rabbitmq]` (depending on broker dependencies). See `WebSocketProgressRecorder` and `websockets.js` for details. ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0 celery-progress-0.1.1/README.md 0000664 0001750 0001750 00000015746 00000000000 016073 0 ustar 00czue czue 0000000 0000000 # Celery Progress Bars for Django Drop in, dependency-free progress bars for your Django/Celery applications. Super simple setup. Lots of customization available. ## Demo [Celery Progress Bar demo on Build With Django](https://buildwithdjango.com/projects/celery-progress/) ### Github demo application: build a download progress bar for Django Starting with Celery can be challenging, [eeintech](https://github.com/eeintech) built a complete [Django demo application](https://github.com/eeintech/django-celery-progress-demo) along with a [step-by-step guide](https://eeinte.ch/stream/progress-bar-django-using-celery/) to get you started on building your own progress bar! ## Installation If you haven't already, make sure you have properly [set up celery in your project](https://docs.celeryproject.org/en/stable/getting-started/first-steps-with-celery.html#first-steps). Then install this library: ```bash pip install celery-progress ``` ## Usage ### Prerequisites First add `celery_progress` to your `INSTALLED_APPS` in `settings.py`. Then add the following url config to your main `urls.py`: ```python from django.urls import re_path, include re_path(r'^celery-progress/', include('celery_progress.urls')), # the endpoint is configurable ``` ### Recording Progress In your task you should add something like this: ```python from celery import shared_task from celery_progress.backend import ProgressRecorder import time @shared_task(bind=True) def my_task(self, seconds): progress_recorder = ProgressRecorder(self) result = 0 for i in range(seconds): time.sleep(1) result += i progress_recorder.set_progress(i + 1, seconds) return result ``` You can add an optional progress description like this: ```python progress_recorder.set_progress(i + 1, seconds, description='my progress description') ``` ### Displaying progress In the view where you call the task you need to get the task ID like so: **views.py** ```python def progress_view(request): result = my_task.delay(10) return render(request, 'display_progress.html', context={'task_id': result.task_id}) ``` Then in the page you want to show the progress bar you just do the following. #### Add the following HTML wherever you want your progress bar to appear: **display_progress.html** ```html
').text('Sum of all seconds is ' + result)
);
}
$(function () {
CeleryProgressBar.initProgressBar(progressUrl, {
onResult: customResult,
})
});
```
## Customization
The `initProgressBar` function takes an optional object of options. The following options are supported:
| Option | What it does | Default Value |
|--------|--------------|---------------|
| pollInterval | How frequently to poll for progress (in milliseconds) | 500 |
| progressBarId | Override the ID used for the progress bar | 'progress-bar' |
| progressBarMessageId | Override the ID used for the progress bar message | 'progress-bar-message' |
| progressBarElement | Override the *element* used for the progress bar. If specified, progressBarId will be ignored. | document.getElementById(progressBarId) |
| progressBarMessageElement | Override the *element* used for the progress bar message. If specified, progressBarMessageId will be ignored. | document.getElementById(progressBarMessageId) |
| resultElementId | Override the ID used for the result | 'celery-result' |
| resultElement | Override the *element* used for the result. If specified, resultElementId will be ignored. | document.getElementById(resultElementId) |
| onProgress | function to call when progress is updated | onProgressDefault |
| onSuccess | function to call when progress successfully completes | onSuccessDefault |
| onError | function to call on a known error with no specified handler | onErrorDefault |
| onRetry | function to call when a task attempts to retry | onRetryDefault |
| onIgnored | function to call when a task result is ignored | onIgnoredDefault |
| onTaskError | function to call when progress completes with an error | onError |
| onNetworkError | function to call on a network error (ignored by WebSocket) | onError |
| onHttpError | function to call on a non-200 response (ignored by WebSocket) | onError |
| onDataError | function to call on a response that's not JSON or has invalid schema due to a programming error | onError |
| onResult | function to call when returned non empty result | CeleryProgressBar.onResultDefault |
| barColors | dictionary containing color values for various progress bar states. Colors that are not specified will defer to defaults | barColorsDefault |
The `barColors` option allows you to customize the color of each progress bar state by passing a dictionary of key-value pairs of `state: #hexcode`. The defaults are shown below.
| State | Hex Code | Image Color |
|-------|----------|:-------------:|
| success | #76ce60 |  |
| error | #dc4f63 |  |
| progress | #68a9ef |  |
| ignored | #7a7a7a |  |
# WebSocket Support
Additionally, this library offers WebSocket support using [Django Channels](https://channels.readthedocs.io/en/latest/)
courtesy of [EJH2](https://github.com/EJH2/).
A working example project leveraging WebSockets is [available here](https://github.com/EJH2/cp_ws-example).
To use WebSockets, install with `pip install celery-progress[websockets,redis]` or
`pip install celery-progress[websockets,rabbitmq]` (depending on broker dependencies).
See `WebSocketProgressRecorder` and `websockets.js` for details.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1625483600.9365168
celery-progress-0.1.1/celery_progress/ 0000775 0001750 0001750 00000000000 00000000000 020006 5 ustar 00czue czue 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0
celery-progress-0.1.1/celery_progress/__init__.py 0000664 0001750 0001750 00000000000 00000000000 022105 0 ustar 00czue czue 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0
celery-progress-0.1.1/celery_progress/backend.py 0000664 0001750 0001750 00000011140 00000000000 021744 0 ustar 00czue czue 0000000 0000000 import datetime
import logging
from abc import ABCMeta, abstractmethod
from decimal import Decimal
from celery.result import EagerResult, allow_join_result
from celery.backends.base import DisabledBackend
logger = logging.getLogger(__name__)
PROGRESS_STATE = 'PROGRESS'
class AbstractProgressRecorder(object):
__metaclass__ = ABCMeta
@abstractmethod
def set_progress(self, current, total, description=""):
pass
class ConsoleProgressRecorder(AbstractProgressRecorder):
def set_progress(self, current, total, description=""):
print('processed {} items of {}. {}'.format(current, total, description))
class ProgressRecorder(AbstractProgressRecorder):
def __init__(self, task):
self.task = task
def set_progress(self, current, total, description=""):
percent = 0
if total > 0:
percent = (Decimal(current) / Decimal(total)) * Decimal(100)
percent = float(round(percent, 2))
state = PROGRESS_STATE
meta = {
'pending': False,
'current': current,
'total': total,
'percent': percent,
'description': description
}
self.task.update_state(
state=state,
meta=meta
)
return state, meta
class Progress(object):
def __init__(self, result):
"""
result:
an AsyncResult or an object that mimics it to a degree
"""
self.result = result
def get_info(self):
response = {'state': self.result.state}
if self.result.state in ['SUCCESS', 'FAILURE']:
success = self.result.successful()
with allow_join_result():
response.update({
'complete': True,
'success': success,
'progress': _get_completed_progress(),
'result': self.result.get(self.result.id) if success else str(self.result.info),
})
elif self.result.state in ['RETRY', 'REVOKED']:
if self.result.state == 'RETRY':
retry = self.result.info
when = str(retry.when) if isinstance(retry.when, datetime.datetime) else str(
datetime.datetime.now() + datetime.timedelta(seconds=retry.when))
result = {'when': when, 'message': retry.message or str(retry.exc)}
else:
result = 'Task ' + str(self.result.info)
response.update({
'complete': True,
'success': False,
'progress': _get_completed_progress(),
'result': result,
})
elif self.result.state == 'IGNORED':
response.update({
'complete': True,
'success': None,
'progress': _get_completed_progress(),
'result': str(self.result.info)
})
elif self.result.state == PROGRESS_STATE:
response.update({
'complete': False,
'success': None,
'progress': self.result.info,
})
elif self.result.state in ['PENDING', 'STARTED']:
response.update({
'complete': False,
'success': None,
'progress': _get_unknown_progress(self.result.state),
})
else:
logger.error('Task %s has unknown state %s with metadata %s', self.result.id, self.result.state, self.result.info)
response.update({
'complete': True,
'success': False,
'progress': _get_unknown_progress(self.result.state),
'result': 'Unknown state {}'.format(self.result.state),
})
return response
class KnownResult(EagerResult):
"""Like EagerResult but supports non-ready states."""
def __init__(self, id, ret_value, state, traceback=None):
"""
ret_value:
result, exception, or progress metadata
"""
# set backend to get state groups (like READY_STATES in ready())
self.backend = DisabledBackend
super().__init__(id, ret_value, state, traceback)
def ready(self):
return super(EagerResult, self).ready()
def __del__(self):
# throws an exception if not overridden
pass
def _get_completed_progress():
return {
'pending': False,
'current': 100,
'total': 100,
'percent': 100,
}
def _get_unknown_progress(state):
return {
'pending': state == 'PENDING',
'current': 0,
'total': 100,
'percent': 0,
}
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1625483600.9325168
celery-progress-0.1.1/celery_progress/static/ 0000775 0001750 0001750 00000000000 00000000000 021275 5 ustar 00czue czue 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1625483600.9365168
celery-progress-0.1.1/celery_progress/static/celery_progress/ 0000775 0001750 0001750 00000000000 00000000000 024504 5 ustar 00czue czue 0000000 0000000 ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0
celery-progress-0.1.1/celery_progress/static/celery_progress/celery_progress.js 0000664 0001750 0001750 00000015671 00000000000 030263 0 ustar 00czue czue 0000000 0000000 class CeleryProgressBar {
constructor(progressUrl, options) {
this.progressUrl = progressUrl;
options = options || {};
let progressBarId = options.progressBarId || 'progress-bar';
let progressBarMessage = options.progressBarMessageId || 'progress-bar-message';
this.progressBarElement = options.progressBarElement || document.getElementById(progressBarId);
this.progressBarMessageElement = options.progressBarMessageElement || document.getElementById(progressBarMessage);
this.onProgress = options.onProgress || this.onProgressDefault;
this.onSuccess = options.onSuccess || this.onSuccessDefault;
this.onError = options.onError || this.onErrorDefault;
this.onTaskError = options.onTaskError || this.onTaskErrorDefault;
this.onDataError = options.onDataError || this.onError;
this.onRetry = options.onRetry || this.onRetryDefault;
this.onIgnored = options.onIgnored || this.onIgnoredDefault;
let resultElementId = options.resultElementId || 'celery-result';
this.resultElement = options.resultElement || document.getElementById(resultElementId);
this.onResult = options.onResult || this.onResultDefault;
// HTTP options
this.onNetworkError = options.onNetworkError || this.onError;
this.onHttpError = options.onHttpError || this.onError;
this.pollInterval = options.pollInterval || 500;
// Other options
let barColorsDefault = {
success: '#76ce60',
error: '#dc4f63',
progress: '#68a9ef',
ignored: '#7a7a7a'
}
this.barColors = Object.assign({}, barColorsDefault, options.barColors);
}
onSuccessDefault(progressBarElement, progressBarMessageElement, result) {
result = this.getMessageDetails(result);
progressBarElement.style.backgroundColor = this.barColors.success;
progressBarMessageElement.textContent = "Success! " + result;
}
onResultDefault(resultElement, result) {
if (resultElement) {
resultElement.textContent = result;
}
}
/**
* Default handler for all errors.
* @param data - A Response object for HTTP errors, undefined for other errors
*/
onErrorDefault(progressBarElement, progressBarMessageElement, excMessage, data) {
progressBarElement.style.backgroundColor = this.barColors.error;
excMessage = excMessage || '';
progressBarMessageElement.textContent = "Uh-Oh, something went wrong! " + excMessage;
}
onTaskErrorDefault(progressBarElement, progressBarMessageElement, excMessage) {
let message = this.getMessageDetails(excMessage);
this.onError(progressBarElement, progressBarMessageElement, message);
}
onRetryDefault(progressBarElement, progressBarMessageElement, excMessage, retryWhen) {
retryWhen = new Date(retryWhen);
let message = 'Retrying in ' + Math.round((retryWhen.getTime() - Date.now())/1000) + 's: ' + excMessage;
this.onError(progressBarElement, progressBarMessageElement, message);
}
onIgnoredDefault(progressBarElement, progressBarMessageElement, result) {
progressBarElement.style.backgroundColor = this.barColors.ignored;
progressBarMessageElement.textContent = result || 'Task result ignored!'
}
onProgressDefault(progressBarElement, progressBarMessageElement, progress) {
progressBarElement.style.backgroundColor = this.barColors.progress;
progressBarElement.style.width = progress.percent + "%";
var description = progress.description || "";
if (progress.current == 0) {
if (progress.pending === true) {
progressBarMessageElement.textContent = 'Waiting for task to start...';
} else {
progressBarMessageElement.textContent = 'Task started...';
}
} else {
progressBarMessageElement.textContent = progress.current + ' of ' + progress.total + ' processed. ' + description;
}
}
getMessageDetails(result) {
if (this.resultElement) {
return ''
} else {
return result || '';
}
}
/**
* Process update message data.
* @return true if the task is complete, false if it's not, undefined if `data` is invalid
*/
onData(data) {
let done = false;
if (data.progress) {
this.onProgress(this.progressBarElement, this.progressBarMessageElement, data.progress);
}
if (data.complete === true) {
done = true;
if (data.success === true) {
this.onSuccess(this.progressBarElement, this.progressBarMessageElement, data.result);
} else if (data.success === false) {
if (data.state === 'RETRY') {
this.onRetry(this.progressBarElement, this.progressBarMessageElement, data.result.message, data.result.when);
done = false;
delete data.result;
} else {
this.onTaskError(this.progressBarElement, this.progressBarMessageElement, data.result);
}
} else {
if (data.state === 'IGNORED') {
this.onIgnored(this.progressBarElement, this.progressBarMessageElement, data.result);
delete data.result;
} else {
done = undefined;
this.onDataError(this.progressBarElement, this.progressBarMessageElement, "Data Error");
}
}
if (data.hasOwnProperty('result')) {
this.onResult(this.resultElement, data.result);
}
} else if (data.complete === undefined) {
done = undefined;
this.onDataError(this.progressBarElement, this.progressBarMessageElement, "Data Error");
}
return done;
}
async connect() {
let response;
try {
response = await fetch(this.progressUrl);
} catch (networkError) {
this.onNetworkError(this.progressBarElement, this.progressBarMessageElement, "Network Error");
throw networkError;
}
if (response.status === 200) {
let data;
try {
data = await response.json();
} catch (parsingError) {
this.onDataError(this.progressBarElement, this.progressBarMessageElement, "Parsing Error")
throw parsingError;
}
const complete = this.onData(data);
if (complete === false) {
setTimeout(this.connect.bind(this), this.pollInterval);
}
} else {
this.onHttpError(this.progressBarElement, this.progressBarMessageElement, "HTTP Code " + response.status, response);
}
}
static initProgressBar(progressUrl, options) {
const bar = new this(progressUrl, options);
bar.connect();
}
}
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0
celery-progress-0.1.1/celery_progress/static/celery_progress/websockets.js 0000664 0001750 0001750 00000001766 00000000000 027225 0 ustar 00czue czue 0000000 0000000 class CeleryWebSocketProgressBar extends CeleryProgressBar {
constructor(progressUrl, options) {
super(progressUrl, options);
}
async connect() {
var ProgressSocket = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' +
window.location.host + this.progressUrl);
ProgressSocket.onopen = function (event) {
ProgressSocket.send(JSON.stringify({'type': 'check_task_completion'}));
};
const bar = this;
ProgressSocket.onmessage = function (event) {
let data;
try {
data = JSON.parse(event.data);
} catch (parsingError) {
bar.onDataError(bar.progressBarElement, bar.progressBarMessageElement, "Parsing Error")
throw parsingError;
}
const complete = bar.onData(data);
if (complete === true || complete === undefined) {
ProgressSocket.close();
}
};
}
}
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0
celery-progress-0.1.1/celery_progress/tasks.py 0000664 0001750 0001750 00000000671 00000000000 021511 0 ustar 00czue czue 0000000 0000000 from celery.signals import task_postrun
@task_postrun.connect(retry=True)
def task_postrun_handler(**kwargs):
"""Runs after a task has finished. This will update the result backend to include the IGNORED result state.
Necessary for HTTP to properly receive ignored task event."""
if kwargs.pop('state') == 'IGNORED':
task = kwargs.pop('task')
task.update_state(state='IGNORED', meta=str(kwargs.pop('retval')))
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625482152.0
celery-progress-0.1.1/celery_progress/urls.py 0000664 0001750 0001750 00000000263 00000000000 021346 0 ustar 00czue czue 0000000 0000000 from django.urls import re_path
from . import views
app_name = 'celery_progress'
urlpatterns = [
re_path(r'^(?P ').text('Sum of all seconds is ' + result)
);
}
$(function () {
CeleryProgressBar.initProgressBar(progressUrl, {
onResult: customResult,
})
});
```
## Customization
The `initProgressBar` function takes an optional object of options. The following options are supported:
| Option | What it does | Default Value |
|--------|--------------|---------------|
| pollInterval | How frequently to poll for progress (in milliseconds) | 500 |
| progressBarId | Override the ID used for the progress bar | 'progress-bar' |
| progressBarMessageId | Override the ID used for the progress bar message | 'progress-bar-message' |
| progressBarElement | Override the *element* used for the progress bar. If specified, progressBarId will be ignored. | document.getElementById(progressBarId) |
| progressBarMessageElement | Override the *element* used for the progress bar message. If specified, progressBarMessageId will be ignored. | document.getElementById(progressBarMessageId) |
| resultElementId | Override the ID used for the result | 'celery-result' |
| resultElement | Override the *element* used for the result. If specified, resultElementId will be ignored. | document.getElementById(resultElementId) |
| onProgress | function to call when progress is updated | onProgressDefault |
| onSuccess | function to call when progress successfully completes | onSuccessDefault |
| onError | function to call on a known error with no specified handler | onErrorDefault |
| onRetry | function to call when a task attempts to retry | onRetryDefault |
| onIgnored | function to call when a task result is ignored | onIgnoredDefault |
| onTaskError | function to call when progress completes with an error | onError |
| onNetworkError | function to call on a network error (ignored by WebSocket) | onError |
| onHttpError | function to call on a non-200 response (ignored by WebSocket) | onError |
| onDataError | function to call on a response that's not JSON or has invalid schema due to a programming error | onError |
| onResult | function to call when returned non empty result | CeleryProgressBar.onResultDefault |
| barColors | dictionary containing color values for various progress bar states. Colors that are not specified will defer to defaults | barColorsDefault |
The `barColors` option allows you to customize the color of each progress bar state by passing a dictionary of key-value pairs of `state: #hexcode`. The defaults are shown below.
| State | Hex Code | Image Color |
|-------|----------|:-------------:|
| success | #76ce60 |  |
| error | #dc4f63 |  |
| progress | #68a9ef |  |
| ignored | #7a7a7a |  |
# WebSocket Support
Additionally, this library offers WebSocket support using [Django Channels](https://channels.readthedocs.io/en/latest/)
courtesy of [EJH2](https://github.com/EJH2/).
A working example project leveraging WebSockets is [available here](https://github.com/EJH2/cp_ws-example).
To use WebSockets, install with `pip install celery-progress[websockets,redis]` or
`pip install celery-progress[websockets,rabbitmq]` (depending on broker dependencies).
See `WebSocketProgressRecorder` and `websockets.js` for details.
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625483600.0
celery-progress-0.1.1/celery_progress.egg-info/SOURCES.txt 0000664 0001750 0001750 00000001227 00000000000 023366 0 ustar 00czue czue 0000000 0000000 LICENSE
MANIFEST.in
README.md
setup.py
celery_progress/__init__.py
celery_progress/backend.py
celery_progress/tasks.py
celery_progress/urls.py
celery_progress/views.py
celery_progress.egg-info/PKG-INFO
celery_progress.egg-info/SOURCES.txt
celery_progress.egg-info/dependency_links.txt
celery_progress.egg-info/requires.txt
celery_progress.egg-info/top_level.txt
celery_progress/static/celery_progress/celery_progress.js
celery_progress/static/celery_progress/websockets.js
celery_progress/websockets/__init__.py
celery_progress/websockets/backend.py
celery_progress/websockets/consumers.py
celery_progress/websockets/routing.py
celery_progress/websockets/tasks.py ././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625483600.0
celery-progress-0.1.1/celery_progress.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 00000000000 025546 0 ustar 00czue czue 0000000 0000000
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625483600.0
celery-progress-0.1.1/celery_progress.egg-info/requires.txt 0000664 0001750 0001750 00000000115 00000000000 024075 0 ustar 00czue czue 0000000 0000000
[rabbitmq]
channels_rabbitmq
[redis]
channels_redis
[websockets]
channels
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625483600.0
celery-progress-0.1.1/celery_progress.egg-info/top_level.txt 0000664 0001750 0001750 00000000020 00000000000 024222 0 ustar 00czue czue 0000000 0000000 celery_progress
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 011452 x ustar 00 0000000 0000000 28 mtime=1625483600.9365168
celery-progress-0.1.1/setup.cfg 0000664 0001750 0001750 00000000046 00000000000 016420 0 ustar 00czue czue 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 011453 x ustar 00 0000000 0000000 22 mtime=1625483297.0
celery-progress-0.1.1/setup.py 0000664 0001750 0001750 00000003352 00000000000 016314 0 ustar 00czue czue 0000000 0000000 import os
from setuptools import find_packages, setup
from glob import glob
readme_name = os.path.join(os.path.dirname(__file__), 'README.md')
with open(readme_name, 'r') as readme:
long_description = readme.read()
# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
setup(
name='celery-progress',
version='0.1.1',
packages=find_packages(),
include_package_data=True,
license='MIT License',
description='Drop in, configurable, dependency-free progress bars for your Django/Celery applications.',
long_description=long_description,
long_description_content_type="text/markdown",
url='https://github.com/czue/celery-progress',
author='Cory Zue',
author_email='cory@coryzue.com',
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
data_files=[
('static/celery_progress', glob('celery_progress/static/celery_progress/*', recursive=True)),
],
extras_require={
'websockets': ['channels'],
'redis': ['channels_redis'],
'rabbitmq': ['channels_rabbitmq']
}
)