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

Fix for missing periodic task name in results #812

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3ab299e
fix for missing periodic task name in results
ntindicator Oct 8, 2024
a9a7490
fix error in admin, add to scheduler headers,tests
ntindicator Oct 10, 2024
3c2dfbb
Add Python 3.13 to the testing (#813)
cclauss Oct 14, 2024
ce728bd
[pre-commit.ci] pre-commit autoupdate (#814)
pre-commit-ci[bot] Oct 14, 2024
9be2125
formatting changes
ntindicator Oct 15, 2024
e3c1ade
[pre-commit.ci] pre-commit autoupdate (#815)
pre-commit-ci[bot] Oct 21, 2024
7f3ba80
[pre-commit.ci] pre-commit autoupdate (#817)
pre-commit-ci[bot] Oct 28, 2024
5f0e38a
fix 'exipres' not working normal as expected (#816)
x-7 Oct 29, 2024
c2cd119
[pre-commit.ci] pre-commit autoupdate (#820)
pre-commit-ci[bot] Nov 4, 2024
476be26
fix long period task will never be triggered (#717)
daydaychen Nov 7, 2024
872ce4d
[pre-commit.ci] pre-commit autoupdate (#823)
pre-commit-ci[bot] Nov 11, 2024
ac0ce20
Bump codecov/codecov-action from 4 to 5 (#825)
dependabot[bot] Nov 15, 2024
c7337cf
[pre-commit.ci] pre-commit autoupdate (#826)
pre-commit-ci[bot] Nov 18, 2024
ca609dd
[pre-commit.ci] pre-commit autoupdate (#827)
pre-commit-ci[bot] Nov 25, 2024
3658923
[pre-commit.ci] pre-commit autoupdate (#829)
pre-commit-ci[bot] Dec 2, 2024
5ea76e8
[pre-commit.ci] pre-commit autoupdate (#830)
pre-commit-ci[bot] Dec 9, 2024
ac9535f
fix for missing periodic task name in results
ntindicator Oct 8, 2024
7167f48
fix error in admin, add to scheduler headers,tests
ntindicator Oct 10, 2024
e6475e6
formatting changes
ntindicator Oct 15, 2024
62f0529
Merge branch 'periodic-task-name-results' of github.com:ntindicator/d…
ntindicator Dec 15, 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
14 changes: 8 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ jobs:
fail-fast: false
matrix: # https://docs.djangoproject.com/en/stable/faq/install/#what-python-version-can-i-use-with-django
django-version: ["3.2", "4.2", "5.0", "5.1"]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.10']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
exclude:
- django-version: "3.2"
python-version: "3.11"
- django-version: "3.2"
python-version: "3.12"
- django-version: "5.0"
python-version: "3.8"
- django-version: "3.2"
python-version: "3.13"
- django-version: "4.2"
python-version: "3.13"
- django-version: "5.0"
python-version: "3.9"
- django-version: "5.1"
python-version: "3.8"
- django-version: "5.0"
python-version: "3.13"
- django-version: "5.1"
python-version: "3.9"

Expand Down Expand Up @@ -55,7 +57,7 @@ jobs:
DJANGO: ${{ matrix.django-version }}
- name: Upload coverage reports to Codecov
if: ${{ matrix.python-version != 'pypy-3.10' }}
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true # optional (default = false)
token: ${{ secrets.CODECOV_TOKEN }}
Expand Down
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ exclude: "migrations"

repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.17.0
rev: v3.19.0
hooks:
- id: pyupgrade
args: ["--py37-plus"]
Expand Down Expand Up @@ -32,23 +32,23 @@ repos:
- id: isort

- repo: https://github.com/adamchainz/django-upgrade
rev: 1.21.0
rev: 1.22.2
hooks:
- id: django-upgrade
args: [--target-version, "3.2"]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.8.2
hooks: # Format before linting
# - id: ruff-format
- id: ruff

- repo: https://github.com/tox-dev/pyproject-fmt
rev: 2.2.4
rev: v2.5.0
hooks:
- id: pyproject-fmt

- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.20.2
rev: v0.23
hooks:
- id: validate-pyproject
15 changes: 11 additions & 4 deletions django_celery_beat/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,18 @@ def run_tasks(self, request, queryset):
return

task_ids = [
task.apply_async(args=args, kwargs=kwargs, queue=queue,
periodic_task_name=periodic_task_name)
task.apply_async(
args=args,
kwargs=kwargs,
queue=queue,
headers={'periodic_task_name': periodic_task_name}
)
if queue and len(queue)
else task.apply_async(args=args, kwargs=kwargs,
periodic_task_name=periodic_task_name)
else task.apply_async(
args=args,
kwargs=kwargs,
headers={'periodic_task_name': periodic_task_name}
)
for task, args, kwargs, queue, periodic_task_name in tasks
]
tasks_run = len(task_ids)
Expand Down
16 changes: 12 additions & 4 deletions django_celery_beat/schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,15 @@ def __init__(self, model, app=None):
if getattr(model, 'expires_', None):
self.options['expires'] = getattr(model, 'expires_')

self.options['headers'] = loads(model.headers or '{}')
self.options['periodic_task_name'] = model.name
headers = loads(model.headers or '{}')
headers['periodic_task_name'] = model.name
self.options['headers'] = headers

self.total_run_count = model.total_run_count
self.model = model

if not model.last_run_at:
model.last_run_at = self._default_now()
model.last_run_at = model.date_changed or self._default_now()
# if last_run_at is not set and
# model.start_time last_run_at should be in way past.
# This will trigger the job to run at start_time
Expand All @@ -110,7 +111,6 @@ def is_due(self):
now = self._default_now()
if getattr(settings, 'DJANGO_CELERY_BEAT_TZ_AWARE', True):
now = maybe_make_aware(self._default_now())

if now < self.model.start_time:
# The datetime is before the start date - don't run.
# send a delay to retry on start_time
Expand All @@ -119,6 +119,14 @@ def is_due(self):
)
return schedules.schedstate(False, delay)

# EXPIRED TASK: Disable task when expired
if self.model.expires is not None:
now = self._default_now()
if now >= self.model.expires:
self._disable(self.model)
# Don't recheck
return schedules.schedstate(False, NEVER_CHECK_TIMEOUT)

# ONE OFF TASK: Disable one off tasks after they've ran once
if self.model.one_off and self.model.enabled \
and self.model.total_run_count > 0:
Expand Down
95 changes: 93 additions & 2 deletions t/unit/test_schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ def test_entry(self):
assert e.options['exchange'] == 'foo'
assert e.options['routing_key'] == 'cpu'
assert e.options['priority'] == 1
assert e.options['headers'] == {'_schema_name': 'foobar'}
assert e.options['periodic_task_name'] == m.name
assert e.options['headers']['_schema_name'] == 'foobar'
assert e.options['headers']['periodic_task_name'] == m.name

right_now = self.app.now()
m2 = self.create_model_interval(
Expand Down Expand Up @@ -213,6 +213,55 @@ def test_entry_and_model_last_run_at_with_utc_no_use_tz(self, monkeypatch):
if hasattr(time, "tzset"):
time.tzset()

@override_settings(
USE_TZ=False,
DJANGO_CELERY_BEAT_TZ_AWARE=False
)
@pytest.mark.usefixtures('depends_on_current_app')
@timezone.override('Europe/Berlin')
@pytest.mark.celery(timezone='Europe/Berlin')
def test_entry_and_model_last_run_at_when_model_changed(self, monkeypatch):
old_tz = os.environ.get("TZ")
os.environ["TZ"] = "Europe/Berlin"
if hasattr(time, "tzset"):
time.tzset()
assert self.app.timezone.key == 'Europe/Berlin'
# simulate last_run_at from DB - not TZ aware but localtime
right_now = datetime.utcnow()
# make sure to use fixed date time
monkeypatch.setattr(self.Entry, '_default_now', lambda o: right_now)
m = self.create_model_crontab(
crontab(minute='*/10')
)
m.save()
e = self.Entry(m, app=self.app)
e.save()
m.refresh_from_db()

# The just created model has no value for date_changed
# so last_run_at should be set to the Entry._default_now()
assert m.last_run_at == e.last_run_at

# If the model has been updated and the entry.last_run_at is None,
# entry.last_run_at should be set to the value of model.date_changed.
# see #717
m2 = self.create_model_crontab(
crontab(minute='*/10')
)
m2.save()
m2.refresh_from_db()
assert m2.date_changed is not None
e2 = self.Entry(m2, app=self.app)
e2.save()
assert m2.last_run_at == m2.date_changed

if old_tz is not None:
os.environ["TZ"] = old_tz
else:
del os.environ["TZ"]
if hasattr(time, "tzset"):
time.tzset()

@override_settings(
USE_TZ=False,
DJANGO_CELERY_BEAT_TZ_AWARE=False,
Expand Down Expand Up @@ -291,6 +340,35 @@ def test_one_off_task(self):
assert not isdue
assert delay == NEVER_CHECK_TIMEOUT

def test_task_with_expires(self):
interval = 10
right_now = self.app.now()
one_second_later = right_now + timedelta(seconds=1)
m = self.create_model_interval(schedule(timedelta(seconds=interval)),
start_time=right_now,
expires=one_second_later)
e = self.Entry(m, app=self.app)
isdue, delay = e.is_due()
assert isdue
assert delay == interval

m2 = self.create_model_interval(schedule(timedelta(seconds=interval)),
start_time=right_now,
expires=right_now)
e2 = self.Entry(m2, app=self.app)
isdue, delay = e2.is_due()
assert not isdue
assert delay == NEVER_CHECK_TIMEOUT

one_second_ago = right_now - timedelta(seconds=1)
m2 = self.create_model_interval(schedule(timedelta(seconds=interval)),
start_time=right_now,
expires=one_second_ago)
e2 = self.Entry(m2, app=self.app)
isdue, delay = e2.is_due()
assert not isdue
assert delay == NEVER_CHECK_TIMEOUT


@pytest.mark.django_db
class test_DatabaseSchedulerFromAppConf(SchedulerCase):
Expand Down Expand Up @@ -869,3 +947,16 @@ def test_run_tasks(self):
assert len(self.request._messages._queued_messages) == 1
queued_message = self.request._messages._queued_messages[0].message
assert queued_message == '2 tasks were successfully run'

@pytest.mark.timeout(5)
def test_run_task_headers(self, monkeypatch):
def mock_apply_async(*args, **kwargs):
self.captured_headers = kwargs.get('headers', {})

monkeypatch.setattr('celery.app.task.Task.apply_async',
mock_apply_async)
ma = PeriodicTaskAdmin(PeriodicTask, self.site)
self.request = self.patch_request(self.request_factory.get('/'))
ma.run_tasks(self.request, PeriodicTask.objects.filter(id=self.m1.id))
assert 'periodic_task_name' in self.captured_headers
assert self.captured_headers['periodic_task_name'] == self.m1.name