-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add schedule task func #22
base: main
Are you sure you want to change the base?
Changes from all commits
c2c375b
1c6a0d9
4e0ce79
2c751c0
87b1f92
51d38f4
177189d
db39bc5
7ce79c9
cc4272b
4a43b40
7d207ec
9151772
b373b24
59a7d4d
b347ab3
c6a5672
b4b7f89
cc884c5
e9293ea
6d4d73e
be9265b
0b905a7
482abeb
acd90ba
1dea5aa
baba136
ac9fe9e
08e0f3c
d6a54ee
368807c
e8cc4ce
13ecb98
77bcd5b
0358efa
3f76f07
9d55d46
5afe70a
3af16f6
35b8600
4e29c0a
a174216
dcf4c42
4dd5fbe
c71c210
4591699
a78a38a
a54b13d
2a43422
cafc91c
d0b7389
6a27375
cbd4c80
78b1976
55d057d
59d4244
b566472
e0f759b
bcd09d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Generated by Django 5.0.7 on 2024-08-23 13:38 | ||
|
||
import django.core.validators | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('django_scheduled_tasks', '0004_scheduledtask_exclusive'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='scheduledtask', | ||
name='day', | ||
field=models.CharField(blank=True, max_length=3, null=True), | ||
), | ||
migrations.AddField( | ||
model_name='scheduledtask', | ||
name='hour', | ||
field=models.SmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(23)]), | ||
), | ||
migrations.AlterField( | ||
model_name='scheduledtask', | ||
name='interval_minutes', | ||
field=models.IntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1440)]), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,14 +15,25 @@ class ScheduledTask(models.Model): | |
"""A record represents a single scheduled (recurring) task.""" | ||
|
||
func = models.CharField(max_length=100, unique=True) # importable function name | ||
interval_minutes = models.IntegerField(validators=[MinValueValidator(1), | ||
MaxValueValidator(1440)]) # 1 min to 24 hours | ||
|
||
onstart = models.BooleanField(default=False) # run at django startup? | ||
enabled = models.BooleanField(default=True) | ||
exclusive = models.BooleanField(default=True) # run as the only task (I.e. thread locked) | ||
|
||
last_timestamp = models.DateTimeField(null=True, blank=True) | ||
last_success = models.BooleanField(null=True, blank=True) | ||
last_runtime = models.FloatField(null=True, blank=True) | ||
day = models.CharField(max_length=3, null=True, blank=True) # Day of the week ('mon', 'tue', etc.) | ||
hour = models.SmallIntegerField( | ||
validators=[MinValueValidator(0), MaxValueValidator(23)], | ||
null=True, | ||
blank=True | ||
) # Hour of the day (24-hour format) | ||
interval_minutes = models.IntegerField( | ||
validators=[MinValueValidator(1), MaxValueValidator(1440)], | ||
null=True, | ||
blank=True | ||
) # 1 min to 24 hours | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add some validation that says you must either have day+hour OR interval_minutes? |
||
_exclusive_lock = Lock() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import logging | ||
import os | ||
from apscheduler.schedulers.background import BackgroundScheduler | ||
from apscheduler.triggers.cron import CronTrigger | ||
from .models import ScheduledTask | ||
|
||
logger = logging.getLogger(__name__) | ||
|
@@ -40,6 +41,11 @@ def add_task(func, minutes, next_run_time): | |
_scheduler.add_job(func, 'interval', minutes=minutes, next_run_time=next_run_time) | ||
|
||
|
||
def add_day_task(func, day, hour, next_run_time): | ||
"""Add a task to our scheduler. Call function 'func' on every 'day' at 'hour' hours.""" | ||
_scheduler.add_job(func, trigger=CronTrigger(day_of_week=day, hour=hour), next_run_time=next_run_time) | ||
|
||
|
||
def _load_tasks(): | ||
"""Load our tasks from the model and add them to the scheduler.""" | ||
tasks = ScheduledTask.objects.filter(enabled=True) | ||
|
@@ -49,5 +55,9 @@ def _load_tasks(): | |
soon = datetime.now() + timedelta(minutes=1) | ||
|
||
for task in tasks: | ||
add_task(task.execute, task.interval_minutes, next_run_time=soon if task.onstart else None) | ||
if task.interval_minutes: | ||
add_task(task.execute, task.interval_minutes, next_run_time=soon if task.onstart else None) | ||
else: | ||
add_day_task(task.execute, task.day, task.hour, next_run_time=soon if task.onstart else None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think a day task should run on start. I think the paradigm implies that it will only run at the specified day/time. |
||
|
||
logging.info(f"Added task '{task}' to background scheduler") |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""Test for django_scheduled_tasks.models.""" | ||
from django.test import TestCase | ||
from django_scheduled_tasks.models import ScheduledTask | ||
from unittest.mock import patch | ||
|
||
|
||
def test_func(): | ||
"""Dummy test function.""" | ||
pass | ||
|
||
|
||
class ScheduledTaskCase(TestCase): | ||
"""Tests for ScheduledTask model.""" | ||
|
||
@classmethod | ||
def setUpTestData(cls): | ||
"""Set up test data.""" | ||
cls.t1 = ScheduledTask.objects.create( | ||
func='django_scheduled_tasks.tests.test_models.test_func', | ||
interval_minutes=10 | ||
) | ||
|
||
def test_str(self): | ||
"""Test the str dunder method.""" | ||
self.assertEqual( | ||
str(self.t1), | ||
f"Run {self.t1.func} every {self.t1.interval_minutes} minutes" | ||
f" ({'enabled' if self.t1.enabled else 'disabled'})" | ||
) | ||
|
||
@patch('django_scheduled_tasks.tests.test_models.test_func') | ||
def test_execute(self, mock_test_func): | ||
"""Test that the execute method runs an instance of the dummy function.""" | ||
self.t1.execute() | ||
mock_test_func.assert_called_once() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""Example test case.""" | ||
from django_scheduled_tasks.models import ScheduledTask | ||
from django_scheduled_tasks.register import register_task, schedule_task | ||
from django.test import TestCase | ||
from unittest.mock import patch | ||
|
||
|
||
class RegisterTaskTest(TestCase): | ||
"""Test case for the `register_task` wrapper.""" | ||
|
||
def test_register_task(self): | ||
"""Checks that the register is empty, and then creates an object.""" | ||
self.assertFalse(ScheduledTask.objects.all().exists()) | ||
|
||
@register_task(1) | ||
def dummy(): | ||
pass | ||
|
||
self.assertEqual(ScheduledTask.objects.count(), 1) | ||
st = ScheduledTask.objects.first() | ||
self.assertEqual(st.func, "django_scheduled_tasks.tests.test_register.dummy") | ||
|
||
@patch('django_scheduled_tasks.register.settings', DISABLE_SCHEDULED_TASKS=True) | ||
def test_register_task_with_disable_register_task(self, mock_settings): | ||
"""Tests that disabling in settings correctly stops a task form being registered.""" | ||
@register_task(1) | ||
def dummy(): | ||
pass | ||
|
||
self.assertFalse(ScheduledTask.objects.all().exists()) | ||
|
||
@patch('django_scheduled_tasks.register.sys', argv=['test']) | ||
def test_resgister_task_with_skipped_args(self, mock_sys_arg): | ||
"""Tests that running with `test` argument stops a task form being registered.""" | ||
@register_task(1) | ||
def dummy(): | ||
pass | ||
|
||
self.assertFalse(ScheduledTask.objects.all().exists()) | ||
|
||
|
||
class ScheduledTaskTestCase(TestCase): | ||
"""Testing the schedule_task function.""" | ||
|
||
def test_schedule_task(self): | ||
"""Test decorator registers task.""" | ||
@schedule_task('mon') | ||
def dummy(): | ||
pass | ||
|
||
self.assertTrue(ScheduledTask.objects.all().exists()) | ||
|
||
def test_schedule_task_with_hour(self): | ||
"""Test decorator registers task with hour argument.""" | ||
@schedule_task('mon', hour=12) | ||
def dummy(): | ||
pass | ||
|
||
self.assertTrue(ScheduledTask.objects.all().exists()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you validate this with choices? https://docs.djangoproject.com/en/4.2/ref/models/fields/#choices