Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-celery-beat for
openSUSE:Factory checked in at 2026-04-01 19:52:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-celery-beat (Old)
and /work/SRC/openSUSE:Factory/.python-django-celery-beat.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-celery-beat"
Wed Apr 1 19:52:36 2026 rev:7 rq:1344087 version:2.9.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-django-celery-beat/python-django-celery-beat.changes
2026-01-05 16:04:16.039222391 +0100
+++
/work/SRC/openSUSE:Factory/.python-django-celery-beat.new.21863/python-django-celery-beat.changes
2026-04-01 19:54:13.274084022 +0200
@@ -1,0 +2,15 @@
+Wed Apr 1 10:08:42 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.9.0:
+ * Added DeepWiki to README
+ * refactor: use in operator instead of regex operator in
+ crontab query to fix mssql regression
+ * refactor: tzaware crontab is due method
+ * feat: enable translation to PeriodicTaskInline verbose name
+ * Remove Python2 'next' assignment in scheduler
+ * Avoid the breaking change in dependency cron_descriptor v2
+ * GitHub Actions: Test on Python 3.14 release candidate 2
+ * Remove upper bound on Django version
+ * Add Django 6.0 support
+
+-------------------------------------------------------------------
Old:
----
django_celery_beat-2.8.1.tar.gz
New:
----
django_celery_beat-2.9.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-celery-beat.spec ++++++
--- /var/tmp/diff_new_pack.HqJAEQ/_old 2026-04-01 19:54:13.886109443 +0200
+++ /var/tmp/diff_new_pack.HqJAEQ/_new 2026-04-01 19:54:13.894109775 +0200
@@ -17,7 +17,7 @@
Name: python-django-celery-beat
-Version: 2.8.1
+Version: 2.9.0
Release: 0
Summary: Database-backed Periodic Tasks
License: BSD-3-Clause
++++++ django_celery_beat-2.8.1.tar.gz -> django_celery_beat-2.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/Changelog
new/django_celery_beat-2.9.0/Changelog
--- old/django_celery_beat-2.8.1/Changelog 2025-05-13 08:57:51.000000000
+0200
+++ new/django_celery_beat-2.9.0/Changelog 2026-02-28 17:44:56.000000000
+0100
@@ -7,6 +7,24 @@
Next
====
+.. _version-2.9.0:
+
+2.9.0
+=====
+:release-date: 2026-02-24
+:release-by: Asif Saif Uddin (@auvipy)
+
+- Added DeepWiki to README
+- refactor: use in operator instead of regex operator in crontab query to fix
mssql regression
+- refactor: tzaware crontab is due method
+- feat: enable translation to PeriodicTaskInline verbose name
+- Remove Python2 'next' assignment in scheduler
+- Avoid the breaking change in dependency cron_descriptor v2
+- GitHub Actions: Test on Python 3.14 release candidate 2
+- Remove upper bound on Django version
+- Add Django 6.0 support
+
+
.. _version-2.8.1:
2.8.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/PKG-INFO
new/django_celery_beat-2.9.0/PKG-INFO
--- old/django_celery_beat-2.8.1/PKG-INFO 2025-05-13 08:57:54.326757700
+0200
+++ new/django_celery_beat-2.9.0/PKG-INFO 2026-02-28 17:44:59.304603600
+0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: django-celery-beat
-Version: 2.8.1
+Version: 2.9.0
Summary: Database-backed Periodic Tasks.
Home-page: https://github.com/celery/django-celery-beat
Author: Asif Saif Uddin, Ask Solem
@@ -18,6 +18,7 @@
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Framework :: Django
@@ -27,6 +28,7 @@
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
+Classifier: Framework :: Django :: 6.0
Classifier: Operating System :: OS Independent
Classifier: Topic :: Communications
Classifier: Topic :: System :: Distributed Computing
@@ -41,8 +43,8 @@
Requires-Dist: backports.zoneinfo; python_version < "3.9"
Requires-Dist: tzdata
Requires-Dist: python-crontab>=2.3.4
-Requires-Dist: cron-descriptor>=1.2.32
-Requires-Dist: Django<6.0,>=2.2
+Requires-Dist: cron-descriptor<2.0.0,>=1.2.32
+Requires-Dist: Django<6.1,>=2.2
Dynamic: author
Dynamic: author-email
Dynamic: classifier
@@ -67,6 +69,7 @@
:Web: http://django-celery-beat.readthedocs.io/
:Download: http://pypi.python.org/pypi/django-celery-beat
:Source: http://github.com/celery/django-celery-beat
+:DeepWiki: |deepwiki|
:Keywords: django, celery, beat, periodic task, cron, scheduling
About
@@ -404,6 +407,11 @@
:alt: Support Python implementations.
:target: http://pypi.python.org/pypi/django-celery-beat/
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+ :alt: Ask http://DeepWiki.com
+ :target: https://deepwiki.com/celery/django-celery-beat
+ :width: 125px
+
django-celery-beat as part of the Tidelift Subscription
-------------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/README.rst
new/django_celery_beat-2.9.0/README.rst
--- old/django_celery_beat-2.8.1/README.rst 2025-05-13 08:57:51.000000000
+0200
+++ new/django_celery_beat-2.9.0/README.rst 2026-02-28 17:44:56.000000000
+0100
@@ -8,6 +8,7 @@
:Web: http://django-celery-beat.readthedocs.io/
:Download: http://pypi.python.org/pypi/django-celery-beat
:Source: http://github.com/celery/django-celery-beat
+:DeepWiki: |deepwiki|
:Keywords: django, celery, beat, periodic task, cron, scheduling
About
@@ -345,6 +346,11 @@
:alt: Support Python implementations.
:target: http://pypi.python.org/pypi/django-celery-beat/
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+ :alt: Ask http://DeepWiki.com
+ :target: https://deepwiki.com/celery/django-celery-beat
+ :width: 125px
+
django-celery-beat as part of the Tidelift Subscription
-------------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat/__init__.py
new/django_celery_beat-2.9.0/django_celery_beat/__init__.py
--- old/django_celery_beat-2.8.1/django_celery_beat/__init__.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/__init__.py 2026-02-28
17:44:56.000000000 +0100
@@ -5,7 +5,7 @@
import re
from collections import namedtuple
-__version__ = '2.8.1'
+__version__ = '2.9.0'
__author__ = 'Asif Saif Uddin, Ask Solem'
__contact__ = '[email protected], [email protected]'
__homepage__ = 'https://github.com/celery/django-celery-beat'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/django_celery_beat/admin.py
new/django_celery_beat-2.9.0/django_celery_beat/admin.py
--- old/django_celery_beat-2.8.1/django_celery_beat/admin.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/admin.py 2026-02-28
17:44:56.000000000 +0100
@@ -281,7 +281,7 @@
can_delete = False
extra = 0
show_change_link = True
- verbose_name = "Periodic Tasks Using This Schedule"
+ verbose_name = _("Periodic Tasks Using This Schedule")
verbose_name_plural = verbose_name
def has_add_permission(self, request, obj):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/django_celery_beat/apps.py
new/django_celery_beat-2.9.0/django_celery_beat/apps.py
--- old/django_celery_beat-2.8.1/django_celery_beat/apps.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/apps.py 2026-02-28
17:44:56.000000000 +0100
@@ -14,5 +14,5 @@
default_auto_field = 'django.db.models.AutoField'
def ready(self):
- from .signals import signals_connect
+ from .signals import signals_connect # noqa: PLC0415
signals_connect()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat/clockedschedule.py
new/django_celery_beat-2.9.0/django_celery_beat/clockedschedule.py
--- old/django_celery_beat-2.8.1/django_celery_beat/clockedschedule.py
2025-05-13 08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/clockedschedule.py
2026-02-28 17:44:56.000000000 +0100
@@ -6,7 +6,7 @@
from .utils import NEVER_CHECK_TIMEOUT
-class clocked(schedules.BaseSchedule):
+class clocked(schedules.BaseSchedule): # noqa: PLW1641
"""clocked schedule.
Depends on PeriodicTask one_off=True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat/schedulers.py
new/django_celery_beat-2.9.0/django_celery_beat/schedulers.py
--- old/django_celery_beat-2.8.1/django_celery_beat/schedulers.py
2025-05-13 08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/schedulers.py
2026-02-28 17:44:56.000000000 +0100
@@ -167,7 +167,6 @@
self.model.total_run_count += 1
self.model.no_changes = True
return self.__class__(self.model)
- next = __next__ # for 2to3
def save(self):
# Object may not be synchronized, so only
@@ -175,7 +174,6 @@
obj = type(self.model)._default_manager.get(pk=self.model.pk)
for field in self.save_fields:
setattr(obj, field, getattr(self.model, field))
-
obj.save()
@classmethod
@@ -314,13 +312,10 @@
]
hours_to_include += [4] # celery's default cleanup task
- # Regex pattern to match only numbers
- # This ensures we only process numeric hour values
- numeric_hour_pattern = r'^\d+$'
-
# Get all tasks with a simple numeric hour value
+ valid_numeric_hours = self._get_valid_hour_formats()
numeric_hour_tasks = CrontabSchedule.objects.filter(
- hour__regex=numeric_hour_pattern
+ hour__in=valid_numeric_hours
)
# Annotate these tasks with their server-hour equivalent
@@ -359,6 +354,15 @@
return exclude_query
+ def _get_valid_hour_formats(self):
+ """
+ Return a list of all valid hour values (0-23).
+ Both zero-padded ("00"–"09") and non-padded ("0"–"23")
+ """
+ return [str(hour) for hour in range(24)] + [
+ f"{hour:02d}" for hour in range(10)
+ ]
+
def _get_unique_timezone_names(self):
"""Get a list of all unique timezone names used in CrontabSchedule"""
return CrontabSchedule.objects.values_list(
@@ -426,7 +430,7 @@
return False
try:
- if ts and ts > (last if last else ts):
+ if ts and ts > (last or ts):
return True
finally:
self._last_timestamp = ts
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat/signals.py
new/django_celery_beat-2.9.0/django_celery_beat/signals.py
--- old/django_celery_beat-2.8.1/django_celery_beat/signals.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/signals.py 2026-02-28
17:44:56.000000000 +0100
@@ -3,10 +3,11 @@
def signals_connect():
"""Connect to signals."""
- from django.db.models import signals
+ from django.db.models import signals # noqa: PLC0415
- from .models import (ClockedSchedule, CrontabSchedule, IntervalSchedule,
- PeriodicTask, PeriodicTasks, SolarSchedule)
+ from .models import (ClockedSchedule, CrontabSchedule, # noqa: PLC0415
+ IntervalSchedule, PeriodicTask, PeriodicTasks,
+ SolarSchedule)
signals.pre_save.connect(
PeriodicTasks.changed, sender=PeriodicTask
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat/tzcrontab.py
new/django_celery_beat-2.9.0/django_celery_beat/tzcrontab.py
--- old/django_celery_beat-2.8.1/django_celery_beat/tzcrontab.py
2025-05-13 08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/tzcrontab.py
2026-02-28 17:44:56.000000000 +0100
@@ -7,7 +7,7 @@
schedstate = namedtuple('schedstate', ('is_due', 'next'))
-class TzAwareCrontab(schedules.crontab):
+class TzAwareCrontab(schedules.crontab): # noqa: PLW1641
"""Timezone Aware Crontab."""
def __init__(
@@ -38,13 +38,7 @@
# convert last_run_at to the schedule timezone
last_run_at = last_run_at.astimezone(self.tz)
- rem_delta = self.remaining_estimate(last_run_at)
- rem = max(rem_delta.total_seconds(), 0)
- due = rem == 0
- if due:
- rem_delta = self.remaining_estimate(self.now())
- rem = max(rem_delta.total_seconds(), 0)
- return schedstate(due, rem)
+ return super().is_due(last_run_at)
# Needed to support pickling
def __repr__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/django_celery_beat/utils.py
new/django_celery_beat-2.9.0/django_celery_beat/utils.py
--- old/django_celery_beat-2.8.1/django_celery_beat/utils.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/utils.py 2026-02-28
17:44:56.000000000 +0100
@@ -57,9 +57,9 @@
"""Return true if Celery is configured to use the db scheduler."""
if not scheduler:
return False
- from kombu.utils import symbol_by_name
+ from kombu.utils import symbol_by_name # noqa: PLC0415
- from .schedulers import DatabaseScheduler
+ from .schedulers import DatabaseScheduler # noqa: PLC0415
return (
scheduler == 'django'
or issubclass(symbol_by_name(scheduler), DatabaseScheduler)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat.egg-info/PKG-INFO
new/django_celery_beat-2.9.0/django_celery_beat.egg-info/PKG-INFO
--- old/django_celery_beat-2.8.1/django_celery_beat.egg-info/PKG-INFO
2025-05-13 08:57:54.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat.egg-info/PKG-INFO
2026-02-28 17:44:59.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: django-celery-beat
-Version: 2.8.1
+Version: 2.9.0
Summary: Database-backed Periodic Tasks.
Home-page: https://github.com/celery/django-celery-beat
Author: Asif Saif Uddin, Ask Solem
@@ -18,6 +18,7 @@
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Framework :: Django
@@ -27,6 +28,7 @@
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
+Classifier: Framework :: Django :: 6.0
Classifier: Operating System :: OS Independent
Classifier: Topic :: Communications
Classifier: Topic :: System :: Distributed Computing
@@ -41,8 +43,8 @@
Requires-Dist: backports.zoneinfo; python_version < "3.9"
Requires-Dist: tzdata
Requires-Dist: python-crontab>=2.3.4
-Requires-Dist: cron-descriptor>=1.2.32
-Requires-Dist: Django<6.0,>=2.2
+Requires-Dist: cron-descriptor<2.0.0,>=1.2.32
+Requires-Dist: Django<6.1,>=2.2
Dynamic: author
Dynamic: author-email
Dynamic: classifier
@@ -67,6 +69,7 @@
:Web: http://django-celery-beat.readthedocs.io/
:Download: http://pypi.python.org/pypi/django-celery-beat
:Source: http://github.com/celery/django-celery-beat
+:DeepWiki: |deepwiki|
:Keywords: django, celery, beat, periodic task, cron, scheduling
About
@@ -404,6 +407,11 @@
:alt: Support Python implementations.
:target: http://pypi.python.org/pypi/django-celery-beat/
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+ :alt: Ask http://DeepWiki.com
+ :target: https://deepwiki.com/celery/django-celery-beat
+ :width: 125px
+
django-celery-beat as part of the Tidelift Subscription
-------------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/django_celery_beat.egg-info/requires.txt
new/django_celery_beat-2.9.0/django_celery_beat.egg-info/requires.txt
--- old/django_celery_beat-2.8.1/django_celery_beat.egg-info/requires.txt
2025-05-13 08:57:54.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat.egg-info/requires.txt
2026-02-28 17:44:59.000000000 +0100
@@ -2,8 +2,8 @@
django-timezone-field>=5.0
tzdata
python-crontab>=2.3.4
-cron-descriptor>=1.2.32
-Django<6.0,>=2.2
+cron-descriptor<2.0.0,>=1.2.32
+Django<6.1,>=2.2
[:python_version < "3.8"]
importlib-metadata<5.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/pyproject.toml
new/django_celery_beat-2.9.0/pyproject.toml
--- old/django_celery_beat-2.8.1/pyproject.toml 2025-05-13 08:57:51.000000000
+0200
+++ new/django_celery_beat-2.9.0/pyproject.toml 2026-02-28 17:44:56.000000000
+0100
@@ -1,6 +1,5 @@
[tool.ruff]
target-version = "py38"
-
lint.select = [
"A", # flake8-builtins
"AIR", # Airflow
@@ -126,26 +125,24 @@
"int",
"str",
]
-lint.pylint.max-args = 8 # Default: 5
-
-[tool.coverage.run]
-branch = true
-cover_pylib = false
-include = [ "*django_celery_beat/*" ]
-omit = [ "django_celery_beat.tests.*" ]
+lint.pylint.max-args = 8 # Default: 5
-[tool.coverage.report]
-exclude_lines = [
- "pragma: no cover",
- "if TYPE_CHECKING:",
+[tool.coverage]
+run.branch = true
+run.cover_pylib = false
+run.include = [ "*django_celery_beat/*" ]
+run.omit = [ "django_celery_beat.tests.*" ]
+report.exclude_lines = [
"except ImportError:",
+ "if TYPE_CHECKING:",
+ "pragma: no cover",
]
-omit = [
- "*/python?.?/*",
- "*/site-packages/*",
- "*/pypy/*",
+report.omit = [
"*/.tox/*",
"*/docker/*",
"*/docs/*",
+ "*/pypy/*",
+ "*/python?.?/*",
+ "*/site-packages/*",
"*/test_*.py",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/requirements/default.txt
new/django_celery_beat-2.9.0/requirements/default.txt
--- old/django_celery_beat-2.8.1/requirements/default.txt 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/default.txt 2026-02-28
17:44:56.000000000 +0100
@@ -4,4 +4,4 @@
backports.zoneinfo; python_version<"3.9"
tzdata
python-crontab>=2.3.4
-cron-descriptor>=1.2.32
+cron-descriptor>=1.2.32,<2.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/requirements/docs.txt
new/django_celery_beat-2.9.0/requirements/docs.txt
--- old/django_celery_beat-2.8.1/requirements/docs.txt 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/docs.txt 2026-02-28
17:44:56.000000000 +0100
@@ -1,4 +1,4 @@
-Django>=2.2,<6.0
+Django>=2.2,<6.1
sphinxcontrib-django
https://github.com/celery/sphinx_celery/archive/master.zip
https://github.com/celery/kombu/zipball/main#egg=kombu
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/requirements/runtime.txt
new/django_celery_beat-2.9.0/requirements/runtime.txt
--- old/django_celery_beat-2.8.1/requirements/runtime.txt 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/runtime.txt 2026-02-28
17:44:56.000000000 +0100
@@ -1 +1 @@
-Django>=2.2,<6.0
+Django>=2.2,<6.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django_celery_beat-2.8.1/requirements/test-django.txt
new/django_celery_beat-2.9.0/requirements/test-django.txt
--- old/django_celery_beat-2.8.1/requirements/test-django.txt 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/test-django.txt 2026-02-28
17:44:56.000000000 +0100
@@ -1 +1 @@
-Django>=3.2,<6.0
+Django>=3.2,<6.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/setup.py
new/django_celery_beat-2.9.0/setup.py
--- old/django_celery_beat-2.8.1/setup.py 2025-05-13 08:57:51.000000000
+0200
+++ new/django_celery_beat-2.9.0/setup.py 2026-02-28 17:44:56.000000000
+0100
@@ -22,6 +22,7 @@
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.13
+ Programming Language :: Python :: 3.14
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Framework :: Django
@@ -31,6 +32,7 @@
Framework :: Django :: 5.0
Framework :: Django :: 5.1
Framework :: Django :: 5.2
+ Framework :: Django :: 6.0
Operating System :: OS Independent
Topic :: Communications
Topic :: System :: Distributed Computing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/t/unit/conftest.py
new/django_celery_beat-2.9.0/t/unit/conftest.py
--- old/django_celery_beat-2.8.1/t/unit/conftest.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/t/unit/conftest.py 2026-02-28
17:44:56.000000000 +0100
@@ -18,7 +18,8 @@
@pytest.fixture(scope='session', autouse=True)
def setup_default_app_trap():
- from celery._state import set_default_app
+ from celery._state import set_default_app # noqa: PLC0415
+
set_default_app(Trap())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django_celery_beat-2.8.1/t/unit/test_schedulers.py
new/django_celery_beat-2.9.0/t/unit/test_schedulers.py
--- old/django_celery_beat-2.8.1/t/unit/test_schedulers.py 2025-05-13
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/t/unit/test_schedulers.py 2026-02-28
17:44:56.000000000 +0100
@@ -2,6 +2,7 @@
import os
import time
from datetime import datetime, timedelta
+from datetime import timezone as dt_timezone
from itertools import count
from time import monotonic
from unittest.mock import patch
@@ -11,6 +12,8 @@
except ImportError:
from backports.zoneinfo import ZoneInfo # Python 3.8
+from unittest.mock import MagicMock
+
import pytest
from celery.schedules import crontab, schedule, solar
from django.contrib.admin.sites import AdminSite
@@ -24,6 +27,7 @@
from django_celery_beat.models import (DAYS, ClockedSchedule, CrontabSchedule,
IntervalSchedule, PeriodicTask,
PeriodicTasks, SolarSchedule)
+from django_celery_beat.tzcrontab import TzAwareCrontab
from django_celery_beat.utils import NEVER_CHECK_TIMEOUT, make_aware
_ids = count(0)
@@ -120,6 +124,167 @@
@pytest.mark.django_db
+class test_TzAwareCrontab_beat_cron_starting_deadline(SchedulerCase):
+ """Tests for TzAwareCrontab with beat_cron_starting_deadline."""
+
+ @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+ @patch("django_celery_beat.tzcrontab.datetime")
+ def test_due_when_within_starting_deadline(self, mock_datetime):
+ """
+ Test that a task is due if last_run_at is within
+ beat_cron_starting_deadline.
+ """
+ # Create a mock app with 5 minute beat_cron_starting_deadline
+ app = MagicMock()
+ app.conf.beat_cron_starting_deadline = 300 # 5 minutes in seconds
+
+ # Set current time to 12:05:50
+ mock_now_utc = datetime(
+ 2023, 10, 26, 12, 5, 50, tzinfo=dt_timezone.utc
+ )
+ mock_datetime.now.return_value = mock_now_utc
+
+ # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+ schedule = TzAwareCrontab(app=app, tz=dt_timezone.utc, minute="*/5")
+
+ # Last run was 290 seconds ago (12:01:00) - within the deadline window
+ last_run_at_utc = mock_now_utc - timedelta(seconds=290)
+
+ # Next scheduled run should be 12:05:00, but current time is 12:05:50
+ # So the task is due, and the deadline check doesn't prevent execution
+ due_status, next_check_delta = schedule.is_due(last_run_at_utc)
+ assert due_status is True
+
+ @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+ @patch("django_celery_beat.tzcrontab.datetime")
+ def test_not_due_when_outside_starting_deadline(self, mock_datetime):
+ """
+ Test that a task is NOT due if last_run_at is outside (older than)
+ beat_cron_starting_deadline.
+ """
+ # Create a mock app with 2 minute beat_cron_starting_deadline
+ app = MagicMock()
+ app.conf.beat_cron_starting_deadline = 120 # 2 minutes in seconds
+
+ # Set current time to 12:09:50
+ mock_now_utc = datetime(
+ 2023, 10, 26, 12, 9, 50, tzinfo=dt_timezone.utc
+ )
+ mock_datetime.now.return_value = mock_now_utc
+
+ # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+ schedule_utc = TzAwareCrontab(
+ app=app, tz=dt_timezone.utc, minute="*/5"
+ )
+
+ # Last run was 310 seconds ago (12:04:40) - outside the deadline window
+ last_run_at_utc = mock_now_utc - timedelta(seconds=310)
+
+ # Next scheduled run after 12:04:40 would be 12:05:00
+ # This is in the past relative to current time, so normally due
+ # BUT since last_run_at is before the deadline window, it's NOT due
+ due_status, next_check_delta = schedule_utc.is_due(last_run_at_utc)
+ assert due_status is False
+
+ @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+ @patch("django_celery_beat.tzcrontab.datetime")
+ def test_not_due_with_recent_run(self, mock_datetime):
+ """
+ Test that a task is not due if last_run_at is recent,
+ even with a starting_deadline set.
+ """
+ # Create a mock app with 5 minute beat_cron_starting_deadline
+ app = MagicMock()
+ app.conf.beat_cron_starting_deadline = 300 # 5 minutes in seconds
+
+ # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+ schedule = TzAwareCrontab(app=app, tz=dt_timezone.utc, minute="*/5")
+
+ # Set current time to 12:04:30 (before next scheduled execution)
+ mock_now_utc_early = datetime(
+ 2023, 10, 26, 12, 4, 30, tzinfo=dt_timezone.utc
+ )
+ mock_datetime.now.return_value = mock_now_utc_early
+
+ # Last run was at 12:04:00
+ # The next schedule would be at 12:05:00, which is in the future
+ last_run_at_recent = datetime(
+ 2023, 10, 26, 12, 4, 0, tzinfo=dt_timezone.utc
+ )
+
+ # Calculate if the task is due
+ # Since the next execution time is in the future, the task is not due
+ # The deadline check doesn't matter for tasks not yet scheduled to run
+ due_status, next_check_delta = schedule.is_due(last_run_at_recent)
+ assert due_status is False
+
+ @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+ @patch("django_celery_beat.tzcrontab.datetime")
+ def test_due_with_no_starting_deadline_set(self, mock_datetime):
+ """
+ Test that a task is due if last_run_at is old and no deadline is set.
+ """
+ # Create a mock app with no beat_cron_starting_deadline
+ app = MagicMock()
+ app.conf.beat_cron_starting_deadline = None
+
+ # Set current time to 12:10:00
+ mock_now_utc = datetime(
+ 2023, 10, 26, 12, 10, 0, tzinfo=dt_timezone.utc
+ )
+ mock_datetime.now.return_value = mock_now_utc
+
+ # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+ schedule_utc = TzAwareCrontab(
+ app=app, tz=dt_timezone.utc, minute="*/5"
+ )
+
+ # Last run was 310 seconds ago (12:04:50)
+ # With no deadline, age of the last run doesn't matter
+ last_run_at_utc = mock_now_utc - timedelta(seconds=310)
+
+ # Next scheduled time after 12:04:50 would be 12:05:00
+ # Current time is 12:10:00, so this is in the past
+ # With no deadline check, the task is due to run
+ due_status, next_check_delta = schedule_utc.is_due(
+ last_run_at_utc
+ )
+ assert due_status is True
+
+ @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+ @patch("django_celery_beat.tzcrontab.datetime")
+ def test_due_with_starting_deadline_non_utc_timezone(self, mock_datetime):
+ """
+ Test with a non-UTC timezone for the schedule.
+ """
+ # Create a mock app with 5 minute beat_cron_starting_deadline
+ app = MagicMock()
+ app.conf.beat_cron_starting_deadline = 300 # 5 minutes in seconds
+ app.timezone = ZoneInfo("America/New_York")
+
+ # Use New York timezone for the schedule
+ schedule_tz = ZoneInfo("America/New_York")
+
+ # Set current time to 08:05:00 New York time
+ mock_now_ny = datetime(2023, 10, 26, 8, 5, 0, tzinfo=schedule_tz)
+ mock_datetime.now.return_value = mock_now_ny
+
+ # Create a schedule that runs every 5 minutes in NY time
+ schedule_ny = TzAwareCrontab(app=app, tz=schedule_tz, minute="*/5")
+
+ # Last run was 290 seconds ago - within deadline window
+ # The deadline window starts at 08:00:00 (current time - 5 minutes)
+ # Since 08:00:10 is after 08:00:00, it's within the deadline window
+ last_run_at_ny = mock_now_ny - timedelta(seconds=290)
+
+ # Next scheduled time after 08:00:10 would be 08:05:00
+ # Current time is 08:05:00, so this is due
+ # The deadline check doesn't prevent execution
+ due_status, next_check_delta = schedule_ny.is_due(last_run_at_ny)
+ assert due_status is True
+
+
[email protected]_db
class test_ModelEntry(SchedulerCase):
Entry = EntryTrackSave
@@ -149,7 +314,7 @@
e2 = self.Entry(m2, app=self.app)
assert e2.last_run_at is right_now
- e3 = e2.next()
+ e3 = next(e2)
assert e3.last_run_at > e2.last_run_at
assert e3.total_run_count == 1
@@ -1064,7 +1229,6 @@
@patch('django.utils.timezone.get_current_timezone')
def test_crontab_timezone_conversion(self, mock_get_tz, mock_aware_now):
# Set up mocks for server timezone and current time
- from datetime import datetime
server_tz = ZoneInfo("Asia/Tokyo")
mock_get_tz.return_value = server_tz
@@ -1141,8 +1305,6 @@
self, mock_aware_now, mock_get_tz
):
# Set up mocks for server timezone and current time
- from datetime import datetime
-
server_tz = ZoneInfo("UTC")
mock_get_tz.return_value = server_tz
@@ -1212,6 +1374,41 @@
"Paris outside window task should be excluded"
)
+ def test_scheduler_valid_hours(self):
+ """Test the _get_valid_hour_formats method."""
+ # Create an instance of DatabaseScheduler
+ s = self.Scheduler(app=self.app)
+
+ # Get the valid hours list
+ valid_hours = s._get_valid_hour_formats()
+
+ # Basic validations
+ # 24 regular hours + 10 zero-padded hours
+ assert len(valid_hours) == 34
+
+ # Check both regular and zero-padded formats are present
+ assert "0" in valid_hours
+ assert "00" in valid_hours
+ assert "9" in valid_hours
+ assert "09" in valid_hours
+ assert "23" in valid_hours
+
+ # Verify all hours 0-23 are included
+ for hour in range(24):
+ assert str(hour) in valid_hours
+
+ # Verify zero-padded hours 00-09 are included
+ for hour in range(10):
+ assert f"{hour:02d}" in valid_hours
+
+ # Check for duplicates (set should have same length as list)
+ assert len(set(valid_hours)) == len(valid_hours)
+
+ # Verify all entries are valid hour representations
+ for hour_str in valid_hours:
+ hour_value = int(hour_str)
+ assert 0 <= hour_value <= 23
+
@pytest.mark.django_db
class test_models(SchedulerCase):
@@ -1465,11 +1662,14 @@
@patch("django_celery_beat.schedulers.aware_now")
def test_server_timezone_handling_with_zoneinfo(self, mock_aware_now):
- """Test handling when server timezone is already a ZoneInfo
instance."""
+ """Test handling when server timezone
+ is already a ZoneInfo instance."""
# Create a mock scheduler with only the methods we need to test
class MockScheduler:
- _get_timezone_offset =
schedulers.DatabaseScheduler._get_timezone_offset
+ _get_timezone_offset = (
+ schedulers.DatabaseScheduler._get_timezone_offset
+ )
s = MockScheduler()
@@ -1490,7 +1690,9 @@
"""Test handling when timezone_name parameter is a ZoneInfo object."""
class MockScheduler:
- _get_timezone_offset =
schedulers.DatabaseScheduler._get_timezone_offset
+ _get_timezone_offset = (
+ schedulers.DatabaseScheduler._get_timezone_offset
+ )
s = MockScheduler()