Skip to content

Commit

Permalink
Django 4.0 and Python 3.10 support (#528)
Browse files Browse the repository at this point in the history
* Replaced pytz with zoneinfo

* Fixed tests

* Added Python 3.10 to test suite

* Removed now-redundant testing requirement

* Bumped minimum django-timezone-field version

* Upgraded pytest

* Addressed points made in the PR

* Added Python 3.10 environment to GitHub Actions

* Removed pytz from new test

* Fixed test

* Added RabbitMQ to GitHub Actions

* Fixed apicheck and linkcheck calling wrong Django version

* Fixed flake8 woes

* Fixed Sphinx generation warnings

Co-authored-by: Bruno Marques <[email protected]>
  • Loading branch information
ElSaico and ElSaico authored Jun 4, 2022
1 parent 10123d3 commit d549016
Show file tree
Hide file tree
Showing 14 changed files with 79 additions and 45 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '3.8', '3.9', 'pypy-3.9']
python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.9']

services:
rabbitmq:
image: rabbitmq
ports:
- "5672:5672"

steps:
- uses: actions/checkout@v3
Expand Down
8 changes: 4 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Important Warning about Time Zones
.. note::
.. note::
This will reset the state as if the periodic tasks have never run before.


Expand Down Expand Up @@ -95,7 +95,7 @@ create the interval object:
.. code-block:: Python
>>> from django_celery_beat.models import PeriodicTask, IntervalSchedule
# executes every 10 seconds.
>>> schedule, created = IntervalSchedule.objects.get_or_create(
... every=10,
Expand Down Expand Up @@ -181,7 +181,7 @@ of a ``30 * * * *`` (execute every 30 minutes) crontab entry you specify:
... day_of_week='*',
... day_of_month='*',
... month_of_year='*',
... timezone=pytz.timezone('Canada/Pacific')
... timezone=zoneinfo.ZoneInfo('Canada/Pacific')
... )
The crontab schedule is linked to a specific timezone using the 'timezone' input parameter.
Expand Down Expand Up @@ -288,7 +288,7 @@ After installation, add ``django_celery_beat`` to Django's settings module:
Run the ``django_celery_beat`` migrations using:

.. code-block:: bash
$ python manage.py migrate django_celery_beat
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.0.3 on 2022-03-21 20:20
# flake8: noqa
from django.db import migrations
import django_celery_beat.models
import timezone_field.fields


class Migration(migrations.Migration):

dependencies = [
('django_celery_beat', '0015_edit_solarschedule_events_choices'),
]

operations = [
migrations.AlterField(
model_name='crontabschedule',
name='timezone',
field=timezone_field.fields.TimeZoneField(
default=
django_celery_beat.models.crontab_schedule_celery_timezone,
help_text=
'Timezone to Run the Cron Schedule on. Default is UTC.',
use_pytz=False, verbose_name='Cron Timezone'),
),
]
16 changes: 10 additions & 6 deletions django_celery_beat/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""Database models."""
try:
from zoneinfo import available_timezones
except ImportError:
from backports.zoneinfo import available_timezones
from datetime import timedelta

import timezone_field
Expand Down Expand Up @@ -66,10 +70,9 @@ def crontab_schedule_celery_timezone():
settings, '%s_TIMEZONE' % current_app.namespace)
except AttributeError:
return 'UTC'
return CELERY_TIMEZONE if CELERY_TIMEZONE in [
choice[0].zone for choice in timezone_field.
TimeZoneField.default_choices
] else 'UTC'
if CELERY_TIMEZONE in available_timezones():
return CELERY_TIMEZONE
return 'UTC'


class SolarSchedule(models.Model):
Expand Down Expand Up @@ -297,6 +300,7 @@ class CrontabSchedule(models.Model):

timezone = timezone_field.TimeZoneField(
default=crontab_schedule_celery_timezone,
use_pytz=False,
verbose_name=_('Cron Timezone'),
help_text=_(
'Timezone to Run the Cron Schedule on. Default is UTC.'),
Expand Down Expand Up @@ -357,8 +361,8 @@ def from_schedule(cls, schedule):
class PeriodicTasks(models.Model):
"""Helper table for tracking updates to periodic tasks.
This stores a single row with ``ident=1``. ``last_update`` is updated
via django signals whenever anything is changed in the :class:`~.PeriodicTask` model.
This stores a single row with ``ident=1``. ``last_update`` is updated via
signals whenever anything changes in the :class:`~.PeriodicTask` model.
Basically this acts like a DB data audit trigger.
Doing this so we also track deletions, and not just insert/update.
"""
Expand Down
6 changes: 1 addition & 5 deletions django_celery_beat/schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,8 @@ def is_due(self):
return self.schedule.is_due(last_run_at_in_tz)

def _default_now(self):
# The PyTZ datetime must be localised for the Django-Celery-Beat
# scheduler to work. Keep in mind that timezone arithmatic
# with a localized timezone may be inaccurate.
if getattr(settings, 'DJANGO_CELERY_BEAT_TZ_AWARE', True):
now = self.app.now()
now = now.tzinfo.localize(now.replace(tzinfo=None))
now = datetime.datetime.now(self.app.timezone)
else:
# this ends up getting passed to maybe_make_aware, which expects
# all naive datetime objects to be in utc time.
Expand Down
9 changes: 3 additions & 6 deletions django_celery_beat/tzcrontab.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from celery import schedules

from collections import namedtuple
from datetime import datetime
import pytz
from datetime import datetime, timezone


schedstate = namedtuple('schedstate', ('is_due', 'next'))
Expand All @@ -14,7 +13,7 @@ class TzAwareCrontab(schedules.crontab):

def __init__(
self, minute='*', hour='*', day_of_week='*',
day_of_month='*', month_of_year='*', tz=pytz.utc, app=None
day_of_month='*', month_of_year='*', tz=timezone.utc, app=None
):
"""Overwrite Crontab constructor to include a timezone argument."""
self.tz = tz
Expand All @@ -28,9 +27,7 @@ def __init__(
)

def nowfunc(self):
return self.tz.normalize(
pytz.utc.localize(datetime.utcnow())
)
return datetime.now(self.tz)

def is_due(self, last_run_at):
"""Calculate when the next run will take place.
Expand Down
8 changes: 4 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
],
extlinks={
'github_project': (
f'https://github.com/%s',
'GitHub project',
'https://github.com/%s',
'GitHub project %s',
),
'github_pr': (
f'https://github.com/celery/django-celery-beat/pull/%s',
'GitHub PR #',
'https://github.com/celery/django-celery-beat/pull/%s',
'GitHub PR #%s',
),
},
extra_intersphinx_mapping={
Expand Down
4 changes: 3 additions & 1 deletion requirements/default.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
celery>=5.2.3,<6.0
django-timezone-field>=4.2.3
django-timezone-field>=5.0
backports.zoneinfo; python_version<"3.9"
tzdata
python-crontab>=2.3.4
1 change: 0 additions & 1 deletion requirements/test-django22.txt

This file was deleted.

5 changes: 2 additions & 3 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
case>=1.3.1
pytest-django<5.0
pytz>dev
pytest<8.0
pytest-django>=2.2,<5.0
pytest>=6.2.5,<8.0
pytest-timeout
ephem
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ def _pyimp():
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Framework :: Django
Framework :: Django :: 4.0
Framework :: Django :: 3.2
Framework :: Django :: 4.0
Operating System :: OS Independent
Topic :: Communications
Topic :: System :: Distributed Computing
Expand Down
2 changes: 1 addition & 1 deletion t/proj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
from django.urls import path

urlpatterns = [
path('admin/', admin.site.urls),
path('admin/', admin.site.urls),
]
21 changes: 12 additions & 9 deletions t/unit/test_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import datetime
import os
try:
from zoneinfo import available_timezones, ZoneInfo
except ImportError:
from backports.zoneinfo import available_timezones, ZoneInfo

from celery import schedules
from django.test import TestCase, override_settings
Expand All @@ -9,8 +14,6 @@
from django.db.migrations.questioner import NonInteractiveMigrationQuestioner
from django.utils import timezone
from django.conf import settings
import pytz, datetime
import timezone_field

from django_celery_beat import migrations as beat_migrations
from django_celery_beat.models import (
Expand Down Expand Up @@ -84,8 +87,7 @@ def _test_duplicate_schedules(self, cls, kwargs, schedule=None):


class CrontabScheduleTestCase(TestCase, TestDuplicatesMixin):
FIRST_VALID_TIMEZONE = timezone_field.\
TimeZoneField.default_choices[0][0].zone
FIRST_VALID_TIMEZONE = available_timezones().pop()

def test_default_timezone_without_settings_config(self):
assert crontab_schedule_celery_timezone() == "UTC"
Expand Down Expand Up @@ -148,11 +150,12 @@ def test_duplicate_schedules(self):
kwargs = {'clocked_time': timezone.now()}
self._test_duplicate_schedules(ClockedSchedule, kwargs)

# IMPORTANT: we must have a valid time-zone (not UTC) to do an accurate testing
@override_settings(TIME_ZONE='Africa/Cairo')
# IMPORTANT: we must have a valid timezone (not UTC) for accurate testing
@override_settings(TIME_ZONE='Africa/Cairo')
def test_timezone_format(self):
"""Make sure the scheduled time is not shown in UTC when time zone is used"""
tz_info = pytz.timezone(settings.TIME_ZONE).localize(datetime.datetime.utcnow())
schedule, created = ClockedSchedule.objects.get_or_create(clocked_time=tz_info)
"""Ensure scheduled time is not shown in UTC when timezone is used"""
tz_info = datetime.datetime.now(ZoneInfo(settings.TIME_ZONE))
schedule, created = ClockedSchedule.objects.get_or_create(
clocked_time=tz_info)
# testnig str(schedule) calls make_aware() internally
assert str(schedule.clocked_time) == str(schedule)
8 changes: 5 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ python =
3.7: py37
3.8: py38, apicheck, linkcheck
3.9: py39, flake8, pydocstyle, cov
3.10: py310
pypy-3.9: pypy3

[gh-actions:env]
DJANGO =
3.2: django32
4.0: django40

[tox]
envlist =
py37-django{32}
py38-django{32,40}
py39-django{32,40}
py310-django{32,40}
pypy3-django{32}
flake8
apicheck
Expand Down Expand Up @@ -43,12 +45,12 @@ commands =


[testenv:apicheck]
basepython = python3.9
basepython = python3.8
commands =
sphinx-build -W -b apicheck -d {envtmpdir}/doctrees docs docs/_build/apicheck

[testenv:linkcheck]
basepython = python3.9
basepython = python3.8
commands =
sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/linkcheck

Expand Down

0 comments on commit d549016

Please sign in to comment.