Compare commits

...
6 Commits
Author SHA1 Message Date
Stan 65d2548e98 Release v1.8.0 2025-04-25 16:30:51 +02:00
Stan Triepels cbe51f995c Move timeout function from ORM broker into class to allow easy customization (#274)
* move timeout function to class to allow customization

* format

* fix format
2025-04-23 15:44:37 +02:00
Adam JohnsonandStan Triepels 0090a6f411 Update tested versions, add python 3.13 support and django 5.2 support. Drop python 3.8 support (#271)
* Update tested versions

* Remove f string

* Fix second `format_html`

---------

Co-authored-by: Stan Triepels <1939656+GDay@users.noreply.github.com>
2025-04-16 01:48:51 +02:00
Fernando Karchiloff a154fd658a Avoid creating task run on Scheduler creation (#268) 2025-03-12 23:31:00 +01:00
Adam Johnson 81f2531e3e Remove disque from CI (#270) 2025-03-12 23:28:59 +01:00
Stan Triepels c464751ae9 Delete deprecated imports and unwrapping (#261)
* delete old code

* remove imports
2025-01-12 03:40:18 +01:00
10 changed files with 92 additions and 44 deletions
+20 -14
View File
@@ -26,25 +26,31 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ] python-version:
django: [ "4.2", "5.0", "5.1"] - "3.9"
- "3.10"
- "3.11"
- "3.12"
django:
- "4.2"
- "5.0"
- "5.1"
- "5.2"
exclude: exclude:
# django 5.1 does not support 3.8 and 3.9 # django 5.2 does not support 3.9
- python-version: "3.8" - python-version: "3.9"
django: "5.1" django: "5.2"
# django 5.1 does not support 3.9
- python-version: "3.9" - python-version: "3.9"
django: "5.1" django: "5.1"
# django 5.0 does not support 3.8 and 3.9 # django 5.0 does not support 3.9
- python-version: "3.8"
django: "5.0"
- python-version: "3.9" - python-version: "3.9"
django: "5.0" django: "5.0"
# django 4.2 does not support 3.13
- python-version: "3.13"
django: "4.2"
services: services:
disque:
image: efrecon/disque:1.0-rc1
ports:
- '7711:7711/tcp'
mongodb: mongodb:
image: mongo image: mongo
ports: ports:
@@ -74,7 +80,7 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install poetry==1.8.5 pip install poetry==1.8.5
poetry add "django==${{ matrix.django }}" --python=${{ matrix.python-version }} poetry add "django~=${{ matrix.django }}a1" --python=${{ matrix.python-version }}
poetry install -E testing poetry install -E testing
- name: Run Tests - name: Run Tests
run: | run: |
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## [v1.8.0](https://github.com/django-q2/django-q2/tree/v1.7.6) (2025-04-25)
- Delete deprecated imports and unwrapping https://github.com/django-q2/django-q2/pull/261
- Remove disque from CI https://github.com/django-q2/django-q2/pull/270
- Avoid creating task run on Scheduler creation https://github.com/django-q2/django-q2/pull/268
- Update tested versions, add python 3.13 support and django 5.2 support. Drop python 3.8 support https://github.com/django-q2/django-q2/pull/271
- Move timeout function from ORM broker into class to allow easy customization https://github.com/django-q2/django-q2/pull/274
## [v1.7.6](https://github.com/django-q2/django-q2/tree/v1.7.6) (2025-01-12) ## [v1.7.6](https://github.com/django-q2/django-q2/tree/v1.7.6) (2025-01-12)
- Make poetry version fixed in CI https://github.com/django-q2/django-q2/pull/260 - Make poetry version fixed in CI https://github.com/django-q2/django-q2/pull/260
+4 -1
View File
@@ -38,7 +38,10 @@ Requirements
- `Django <https://www.djangoproject.com>`__ > = 4.2 - `Django <https://www.djangoproject.com>`__ > = 4.2
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__ - `Django-picklefield <https://github.com/gintas/django-picklefield>`__
Tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 4.2.X and 5.0.X Tested with:
* Python 3.9 to 3.13.
* Django 4.2 to 5.2.
Brokers Brokers
~~~~~~~ ~~~~~~~
+1 -6
View File
@@ -1,8 +1,3 @@
import django VERSION = (1, 8, 0)
VERSION = (1, 7, 6)
if django.VERSION < (3, 2):
default_app_config = "django_q.apps.DjangoQConfig"
__all__ = ["conf", "cluster", "models", "tasks"] __all__ = ["conf", "cluster", "models", "tasks"]
+1 -1
View File
@@ -138,7 +138,7 @@ class ScheduleAdmin(admin.ModelAdmin):
url = reverse("admin:django_q_success_change", args=(obj.task_id,)) url = reverse("admin:django_q_success_change", args=(obj.task_id,))
else: else:
url = reverse("admin:django_q_failure_change", args=(obj.task_id,)) url = reverse("admin:django_q_failure_change", args=(obj.task_id,))
return format_html(f'<a href="{url}">[{obj.task_name}]</a>') return format_html('<a href="{}">[{}]</a>', url, obj.task_name)
return None return None
get_last_run.allow_tags = True get_last_run.allow_tags = True
+4 -5
View File
@@ -10,10 +10,6 @@ from django_q.conf import Conf, logger
from django_q.models import OrmQ from django_q.models import OrmQ
def _timeout():
return timezone.now() + timedelta(seconds=Conf.RETRY)
class ORM(Broker): class ORM(Broker):
@staticmethod @staticmethod
def get_connection(list_key: str = None): def get_connection(list_key: str = None):
@@ -28,6 +24,9 @@ class ORM(Broker):
logger.debug("Broker in an atomic transaction") logger.debug("Broker in an atomic transaction")
return OrmQ.objects.using(Conf.ORM) return OrmQ.objects.using(Conf.ORM)
def timeout(self, task):
return timezone.now() + timedelta(seconds=Conf.RETRY)
def queue_size(self) -> int: def queue_size(self) -> int:
return ( return (
self.get_connection() self.get_connection()
@@ -75,7 +74,7 @@ class ORM(Broker):
if ( if (
self.get_connection() self.get_connection()
.filter(id=task.id, lock=task.lock) .filter(id=task.id, lock=task.lock)
.update(lock=_timeout()) .update(lock=self.timeout(task))
): ):
task_list.append((task.pk, task.payload)) task_list.append((task.pk, task.payload))
# else don't process, as another cluster has been faster than us on # else don't process, as another cluster has been faster than us on
+8 -12
View File
@@ -2,7 +2,6 @@ from datetime import datetime, timedelta
from keyword import iskeyword from keyword import iskeyword
# Django # Django
from django import get_version
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
@@ -15,7 +14,6 @@ from django.utils.translation import gettext_lazy as _
# External # External
from picklefield import PickledObjectField from picklefield import PickledObjectField
from picklefield.fields import dbsafe_decode
# Local # Local
from django_q.conf import croniter from django_q.conf import croniter
@@ -59,7 +57,7 @@ class Task(models.Model):
.exclude(success=False) .exclude(success=False)
.values_list("result", flat=True) .values_list("result", flat=True)
) )
return decode_results(values) return values
def group_result(self, failures=False): def group_result(self, failures=False):
if self.group: if self.group:
@@ -298,12 +296,18 @@ class Schedule(models.Model):
url = reverse("admin:django_q_success_change", args=(task.id,)) url = reverse("admin:django_q_success_change", args=(task.id,))
else: else:
url = reverse("admin:django_q_failure_change", args=(task.id,)) url = reverse("admin:django_q_failure_change", args=(task.id,))
return format_html(f'<a href="{url}">[{task.name}]</a>') return format_html('<a href="{}">[{}]</a>', url, task.name)
return None return None
def __str__(self): def __str__(self):
return self.func return self.func
def save(self, *args, **kwargs):
if self.pk is None and self.schedule_type == self.CRON:
self.next_run = self.calculate_next_run()
return super().save(*args, **kwargs)
success.boolean = True success.boolean = True
success.short_description = _("success") success.short_description = _("success")
last_run.allow_tags = True last_run.allow_tags = True
@@ -356,11 +360,3 @@ class OrmQ(models.Model):
app_label = "django_q" app_label = "django_q"
verbose_name = _("Queued task") verbose_name = _("Queued task")
verbose_name_plural = _("Queued tasks") verbose_name_plural = _("Queued tasks")
# Backwards compatibility for Django 1.7
def decode_results(values):
if get_version().split(".")[1] == "7":
# decode values in 1.7
return [dbsafe_decode(v) for v in values]
return values
+41
View File
@@ -473,6 +473,47 @@ def test_scheduler_atomic_must_specify_the_database_based_on_router_redirection(
mocked_db.atomic.assert_called_with(using="default") mocked_db.atomic.assert_called_with(using="default")
@pytest.mark.django_db
def test_schedule_save_sets_next_run_for_cron():
"""Ensure Schedule.save() sets next_run correctly for CRON schedules."""
cron_expression = "0 12 * * *" # Executes dialy at 12pm
schedule = Schedule(
func="math.sqrt",
schedule_type=Schedule.CRON,
cron=cron_expression,
)
assert schedule.next_run is not None
assert schedule.pk is None
initial_next_run = schedule.next_run
schedule.save()
# After save, next_run must be recalculated based on the CRON expression
assert schedule.next_run > initial_next_run
@pytest.mark.django_db
def test_schedule_save_direct_db(broker):
"""Ensure Schedule.save() updates next_run correctly when created directly in DB."""
cron_expression = "0 12 * * *" # Executes dialy at 12pm
# Creating schedule directly in database
schedule = Schedule.objects.create(
func="math.sqrt",
schedule_type=Schedule.CRON,
cron=cron_expression,
)
# Next run must be defined after execution time
assert schedule.next_run is not None
assert schedule.next_run > timezone.now()
scheduler(broker)
assert broker.queue_size() == 0
def test_localtime(): def test_localtime():
assert not is_naive(localtime()) assert not is_naive(localtime())
+2 -2
View File
@@ -73,9 +73,9 @@ author = "Ilan Steemers, Stan Triepels"
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = "1.7" version = "1.8"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = "1.7.6" release = "1.8.0"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
+3 -3
View File
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry] [tool.poetry]
name = "django-q2" name = "django-q2"
version = "1.7.6" version = "1.8.0"
packages = [ packages = [
{ include = "django_q" }, { include = "django_q" },
] ]
@@ -30,11 +30,11 @@ classifiers = [
'Operating System :: MacOS', 'Operating System :: MacOS',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
'Topic :: System :: Distributed Computing', 'Topic :: System :: Distributed Computing',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
@@ -52,7 +52,7 @@ include = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.8,<4" python = ">=3.9,<4"
django = ">=4.2, <6" django = ">=4.2, <6"
django-picklefield = "^3.1" django-picklefield = "^3.1"