Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a17a6c2
Showing
12 changed files
with
549 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
|
||
# Created by https://www.gitignore.io/api/python,django | ||
|
||
### Django ### | ||
*.log | ||
*.pot | ||
*.pyc | ||
__pycache__/ | ||
local_settings.py | ||
db.sqlite3 | ||
media | ||
|
||
### Python ### | ||
# Byte-compiled / optimized / DLL files | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
env/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*,cover | ||
.hypothesis/ | ||
|
||
# Translations | ||
*.mo | ||
|
||
# Django stuff: | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# dotenv | ||
.env | ||
|
||
# virtualenv | ||
.venv | ||
venv/ | ||
ENV/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# End of https://www.gitignore.io/api/python,django | ||
|
||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
Scheduler | ||
========= | ||
Task Scheduler for Django | ||
------------------------- | ||
|
||
Scheduler is a simple Django app for scheduling tasks at a specific time or repeating in a pattern based on the rules defined in **Recurrence Rule _(RRULE)_** syntax as per the [RFC2445](https://tools.ietf.org/html/rfc2445) standard. | ||
|
||
# Special Thanks | ||
* [Django](https://github.com/django/django/) - ORM and app system. | ||
* [Celery](https://github.com/celery/celery/) - Task management. | ||
* [Dateutil](https://github.com/dateutil/dateutil/) - Interpreting _RRULE_ syntax. | ||
|
||
# Requirements | ||
* [Django](https://github.com/django/django/) 1.8+ | ||
* [Celery](https://github.com/celery/celery/) 4.0+ | ||
|
||
# Usage | ||
* Add the `celery.task` or `shared_task` decorator and specify `scheduler.tasks.RepeatTask` as the base. | ||
```python | ||
from celery import shared_task | ||
from scheduler.tasks import RepeatTask | ||
|
||
@shared_task(base=RepeatTask) | ||
def say_hello(fruit): | ||
print(f'Hello, I ate {fruit} today.') | ||
``` | ||
* Make sure atleast one Celery worker is running. Restart your workers so that the changes are reflected in the workers. | ||
* Use `TaskScheduler` to schedule the task. You can specify the starting time, the ending time and the recurrence rule for the task. Arguments and keyword arguments should be specified separately. Task description is mandatory. The task scheduler returns a `task_id` which can be used to cancel the task. | ||
```python | ||
from datetime import timedelta | ||
|
||
from django.utils import timezone | ||
from scheduler.scheduler import TaskScheduler | ||
|
||
now = timezone.now() + timedelta(hours=3) | ||
tomorrow = now + timedelta(hours=7) | ||
|
||
task_id = TaskScheduler.schedule(say_hello, | ||
description='Speaks out which fruit you ate today', | ||
args=('Apple', ) | ||
kwargs=None, | ||
rrule_string='RRULE:FREQ=HOURLY;INTERVAL=2', # Run every 2 hours... | ||
trigger_at=now, # ...starting 3 hours from now... | ||
until=tomorrow # ...till 10 hours from now.. | ||
) | ||
print(f'Task ID: {task_id}') # Task ID: UUID('53f81020-1b77-45cd-8f0d-2c5a8acee6a8') | ||
``` | ||
* Suppose, you run the above snippet at `2017-03-25 01:30:00`, the task will execute at the following times, | ||
``` | ||
[2017-03-25 04:30:00] Hello, I ate Apple today. | ||
[2017-03-25 06:30:00] Hello, I ate Apple today. | ||
[2017-03-25 08:30:00] Hello, I ate Apple today. | ||
[2017-03-25 10:30:00] Hello, I ate Apple today. | ||
``` | ||
* To run this task immediately, set `trigger_at` as `None`. | ||
* To run this task infinitely, set `until` as `None`. | ||
* To cancel a schedule, use `TaskScheduler.cancel` method and pass the `task_id` that was generated by the `TaskScheduler`. | ||
```python | ||
TaskScheduler.cancel(task_id) | ||
``` | ||
|
||
# API | ||
## `TaskScheduler.schedule` | ||
**Signature:** `TaskScheduler.schedule(func, description, args=None, kwargs=None, rrule_string=None, trigger_at=None, until=None)` | ||
Parameter | Type | Description | ||
--- | --- | -- | ||
`func` | `function` | **Required.** Task function. | ||
`description` | `str` | **Required.** Description of the task. **This may get deprecated in future.** | ||
`args` | `list` or `tuple` | **Optional.** **Default:** `None`. Arguments to be passed to `func`. **All values should be JSON serializable.** | ||
`kwargs` | `dict` | **Optional.** **Default:** `None`. Keyword arguments to be passed to `func`. **All values should be JSON serializable.** | ||
`rrule_string` | `str` | **Optional.** **Default:** `None`. Recurrence rule. `func` will be executed as per the pattern defined by this rule. If not specified, `func` will be executed only once. | ||
`trigger_at` | `datetime` with `tzinfo` | **Optional.** **Default:** `None`. Sets the datetime for the first run of the task. If not specified, it defaults to `datetime.now()`. | ||
`until` | `datetime` with `tzinfo` | **Optional.** **Default:** `None`. Sets the end time for the task. The task will never execute after this datetime. If not specified, `task` will run as long as it is defined in the recurrence rule. | ||
|
||
> **NOTE:** It is important that all the `datetime` instances passed should have timezone information, i. e., they should be [timezone aware](https://docs.djangoproject.com/en/1.10/topics/i18n/timezones/#naive-and-aware-datetime-objects). Use Django's [`timezone.now()`](https://docs.djangoproject.com/en/1.10/ref/utils/#django.utils.timezone.now) utility to get a timezone aware instance with `USE_TZ=True` in settings. | ||
# Limitations | ||
* Currently, there is no support for `DTSTART` and `UNTIL` rules. Use `trigger_at` and `until` parameters. Even if you specify `DTSTART` and `UNTIL` rules, they will get overidden. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from django.contrib import admin | ||
|
||
from .models import ( | ||
ScheduledTask, | ||
ScheduledTaskRunLog | ||
) | ||
|
||
|
||
class ScheduledTaskRunLogTabularInline(admin.TabularInline): | ||
readonly_fields = [field.name | ||
for field in ScheduledTaskRunLog._meta.fields | ||
if field.name != 'id'] | ||
ordering = ('created_at', ) | ||
model = ScheduledTaskRunLog | ||
|
||
|
||
@admin.register(ScheduledTask) | ||
class ScheduledTaskModelAdmin(admin.ModelAdmin): | ||
list_display = [field.name for field in ScheduledTask._meta.fields] | ||
inlines = (ScheduledTaskRunLogTabularInline, ) | ||
ordering = ('-created_at', ) | ||
model = ScheduledTask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class SchedulerConfig(AppConfig): | ||
name = 'scheduler' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.10.6 on 2017-03-26 10:02 | ||
from __future__ import unicode_literals | ||
|
||
import django.contrib.postgres.fields.jsonb | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import uuid | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='ScheduledTask', | ||
fields=[ | ||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||
('function_name', models.CharField(max_length=1000)), | ||
('description', models.TextField(blank=True, null=True)), | ||
('args', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, null=True)), | ||
('kwargs', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, null=True)), | ||
('rrule', models.TextField(blank=True, null=True)), | ||
('status', models.CharField(choices=[('created', 'Created'), ('running', 'Running'), ('success', 'Success'), ('failure', 'Failure'), ('cancelled', 'Cancelled')], default='created', max_length=255)), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('updated_at', models.DateTimeField(auto_now=True)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='ScheduledTaskRunLog', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('task_id', models.UUIDField()), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('scheduled_task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasker.ScheduledTask')), | ||
], | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from __future__ import unicode_literals | ||
|
||
from uuid import uuid4 | ||
|
||
from django.contrib.postgres.fields import JSONField | ||
from django.db import models | ||
|
||
|
||
class ScheduledTask(models.Model): | ||
id = models.UUIDField(primary_key=True, editable=False, default=uuid4) | ||
function_name = models.CharField(max_length=1000) | ||
description = models.TextField(blank=True, null=True) | ||
|
||
args = JSONField(blank=True, null=True, default=dict) | ||
kwargs = JSONField(blank=True, null=True, default=dict) | ||
rrule = models.TextField(blank=True, null=True) | ||
|
||
STATUS_CREATED = 'created' | ||
STATUS_RUNNING = 'running' | ||
STATUS_SUCCESS = 'success' | ||
STATUS_FAILURE = 'failure' | ||
STATUS_CANCELLED = 'cancelled' | ||
STATUS_CHOICES = ( | ||
(STATUS_CREATED, 'Created'), | ||
(STATUS_RUNNING, 'Running'), | ||
(STATUS_SUCCESS, 'Success'), | ||
(STATUS_FAILURE, 'Failure'), | ||
(STATUS_CANCELLED, 'Cancelled'), | ||
) | ||
|
||
status = models.CharField(max_length=255, choices=STATUS_CHOICES, | ||
default=STATUS_CREATED) | ||
|
||
created_at = models.DateTimeField(auto_now_add=True) | ||
updated_at = models.DateTimeField(auto_now=True) | ||
|
||
def __unicode__(self): | ||
return self.function_name | ||
|
||
def __str__(self): | ||
return self.function_name | ||
|
||
def save_status(self, status): | ||
"""Sets and saves the status to the scheduled task. | ||
Arguments: | ||
status (str): Status. | ||
Returns: | ||
ScheduledTask: Current scheduled task instance. | ||
""" | ||
self.status = status | ||
self.save(update_fields=('status', )) | ||
return self | ||
|
||
|
||
class ScheduledTaskRunLog(models.Model): | ||
task_id = models.UUIDField() | ||
scheduled_task = models.ForeignKey(ScheduledTask) | ||
|
||
created_at = models.DateTimeField(auto_now_add=True) | ||
|
||
def __unicode__(self): | ||
return str(self.task_id) | ||
|
||
def __str__(self): | ||
return str(self.task_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
celery==4.0.2 | ||
Django==1.10.6 | ||
python-dateutil==2.6.0 |
Oops, something went wrong.