Skip to content
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

Open
wants to merge 59 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c2c375b
test file for register
j43333 Aug 20, 2024
1c6a0d9
testing wrapper
j43333 Aug 21, 2024
4e0ce79
testing wrapper
j43333 Aug 21, 2024
2c751c0
fixing flex8
j43333 Aug 21, 2024
87b1f92
fixing flex8
j43333 Aug 21, 2024
51d38f4
fixing flex8
j43333 Aug 21, 2024
177189d
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 21, 2024
db39bc5
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 21, 2024
7ce79c9
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 21, 2024
cc4272b
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 21, 2024
4a43b40
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 21, 2024
7d207ec
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 21, 2024
9151772
fixing flex8
j43333 Aug 22, 2024
b373b24
merging with remote
j43333 Aug 22, 2024
59a7d4d
fixing flex8
j43333 Aug 22, 2024
b347ab3
fixing flex8
j43333 Aug 22, 2024
c6a5672
fixing flex8
j43333 Aug 22, 2024
b4b7f89
fixing flex8
j43333 Aug 22, 2024
cc884c5
fixing flex8
j43333 Aug 22, 2024
e9293ea
fixing flex8
j43333 Aug 22, 2024
6d4d73e
fixing flex8
j43333 Aug 22, 2024
be9265b
Update django_scheduled_tasks/tests/test_register.py
j43333 Aug 22, 2024
0b905a7
whitespace
Aug 22, 2024
482abeb
init
Aug 22, 2024
acd90ba
planning how im going to make the function
j43333 Aug 23, 2024
1dea5aa
Merge pull request #19 from bear-rsg/add-schedule-task-func
j43333 Aug 23, 2024
baba136
Merge pull request #17 from bear-rsg/register_tests
LeoTurnell-Ritson Aug 23, 2024
ac9fe9e
renamed dummy test file
j43333 Aug 19, 2024
08e0f3c
tests added for scheduled tasks
j43333 Aug 19, 2024
d6a54ee
Add test print
j43333 Aug 19, 2024
368807c
use patch
j43333 Aug 19, 2024
e8cc4ce
fixing flex
j43333 Aug 20, 2024
13ecb98
fixing flex
j43333 Aug 20, 2024
77bcd5b
fixing flex
j43333 Aug 20, 2024
0358efa
fixing flex
j43333 Aug 20, 2024
3f76f07
fixing flex
j43333 Aug 20, 2024
9d55d46
fixing flex
j43333 Aug 20, 2024
5afe70a
fixing flex
j43333 Aug 20, 2024
3af16f6
fixing flex
j43333 Aug 20, 2024
35b8600
fixing flex
j43333 Aug 20, 2024
4e29c0a
Update django_scheduled_tasks/tests/test_models.py
j43333 Aug 20, 2024
a174216
Update django_scheduled_tasks/tests/test_models.py
j43333 Aug 20, 2024
dcf4c42
Update django_scheduled_tasks/tests/test_models.py
j43333 Aug 20, 2024
4dd5fbe
Update django_scheduled_tasks/tests/test_models.py
j43333 Aug 20, 2024
c71c210
Update django_scheduled_tasks/tests/test_models.py
j43333 Aug 20, 2024
4591699
update schedule_task function
j43333 Aug 23, 2024
a78a38a
updated schedule_tasks function and started on the test for it.
j43333 Aug 23, 2024
a54b13d
tests for schedule_task func
j43333 Aug 23, 2024
2a43422
schedule_task func
j43333 Aug 23, 2024
cafc91c
Merge remote-tracking branch 'origin/add-schedule-task-func' into add…
Aug 23, 2024
d0b7389
consolodated tests
Aug 23, 2024
6a27375
rm comments
Aug 23, 2024
cbd4c80
add add_day_task(...) func
Aug 23, 2024
78b1976
add day and hour fields, with migrations
Aug 23, 2024
55d057d
add test and fixes
Aug 23, 2024
59d4244
flake8
Aug 23, 2024
b566472
flake8
Aug 23, 2024
e0f759b
merge with origin/feature/*
Aug 23, 2024
bcd09d1
docstring
Aug 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)]),
),
]
15 changes: 13 additions & 2 deletions django_scheduled_tasks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor

Choose a reason for hiding this comment

The 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()

Expand Down
30 changes: 30 additions & 0 deletions django_scheduled_tasks/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
logger = logging.getLogger(__name__)


DEFAULT_SCHEDULE_HOUR = 2


def register_task(interval, onstart=False):
"""
Register a scheduled task via this decorator.
Expand Down Expand Up @@ -45,3 +48,30 @@ def wrapper(func):

return func
return wrapper


def schedule_task(day, hour=DEFAULT_SCHEDULE_HOUR, onstart=False):
"""Add a scheduled task for a specific day of the week.

Args:
day (str): Day of the week shorthand 'mon', 'tue', 'wed', etc.
hour (int, optional): Hour of the day, 24-hour clock, defaults to `DEFAULT_SCHEDULE_HOUR`.
onstart (bool, optional): Should this be run at startup, defaults to `False`.
"""
def wrapper(func):
try:
desc = f'{func.__module__}.{func.__name__}'
ScheduledTask.objects.create(
func=desc,
day=day,
hour=hour,
onstart=onstart
)
except IntegrityError:
# Already registered
pass
else:
logging.info(f"Registered scheduled task {desc} for day {day} at hour {hour}.")

return func
return wrapper
12 changes: 11 additions & 1 deletion django_scheduled_tasks/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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")
10 changes: 0 additions & 10 deletions django_scheduled_tasks/tests/test_fake.py

This file was deleted.

35 changes: 35 additions & 0 deletions django_scheduled_tasks/tests/test_models.py
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()
59 changes: 59 additions & 0 deletions django_scheduled_tasks/tests/test_register.py
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())
Loading