Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 7 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: '3.9'
architecture: 'x64'
python-version: "3.x"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
pip install build twine

- name: Build source and binary distribution package
run: |
python setup.py sdist bdist_wheel
python -m build
env:
PACKAGE_VERSION: ${{ github.ref }}

Expand Down
21 changes: 4 additions & 17 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,17 @@ jobs:
fail-fast: false
matrix:
python-version:
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'
django-version:
- '4.2'
- '5.0'
- '5.1'
exclude:
- python-version: '3.9'
django-version: '5.0'
- python-version: '3.9'
django-version: '5.1'
- '5.2'

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
Expand All @@ -34,23 +28,16 @@ jobs:
- name: Install dependencies and package
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -e ".[dev]"
pip install django~=${{ matrix.django-version }}.0

- name: Run lint and code review
run: |
pre-commit run --all-files
- name: Run tests with coverage
run: |
# prepare Django project: link all necessary data from the test project into the root directory
# Hint: Simply changing the directory does not work (leads to missing files in coverage report)
ln -s ./tests/core core
ln -s ./tests/testapp testapp
ln -s ./tests/manage.py manage.py
# run tests with coverage
coverage run \
--source='./django_future_tasks' \
manage.py test
pytest --cov=django_future_tasks tests
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ __pycache__
db.sqlite3
build/*
tests/static/*
.python-version
.coverage
uv.lock
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ repos:
- id: add-trailing-comma

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
rev: v0.12.8
hooks:
- id: ruff
- id: ruff-check
args: [ --fix ]
- id: ruff-format
34 changes: 33 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- Use `timezone.now()` instead of `datetime.now()` in `PeriodicFutureTask.save()`.
- Use `condition` instead of `check` attribute in `CheckConstraint` with `django>=5.1`.

### Added

- Support for Python 3.14.
- Support for Django 5.2.

### Removed

- Support for Python 3.9.
- Support for Django 5.0.

## [1.3.2]

### Changed

- Migrate to `pytest`.
- Migrate to `pyproject.toml`.

## [1.3.1]

### Fixed

- Fix infinite loop in populate_periodic_future_tasks on IntegrityError

## [1.3.0]

### Added
Expand Down Expand Up @@ -57,7 +87,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial setup.

[Unreleased]: https://github.com/anexia/django-future-tasks/compare/1.3.0...HEAD
[Unreleased]: https://github.com/anexia/django-future-tasks/compare/1.3.2...HEAD
[1.3.2]: https://github.com/anexia/django-future-tasks/releases/tag/1.3.2
[1.3.1]: https://github.com/anexia/django-future-tasks/releases/tag/1.3.1
Comment on lines -60 to +92
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I found the unreleased section. 🙃 I can change this in the other pull request and rebase next week.

[1.3.0]: https://github.com/anexia/django-future-tasks/releases/tag/1.3.0
[1.2.1]: https://github.com/anexia/django-future-tasks/releases/tag/1.2.1
[1.2.0]: https://github.com/anexia/django-future-tasks/releases/tag/1.2.0
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ python manage.py populate_periodic_future_tasks

If your project uses an older version of Django or Django Rest Framework, you can choose an older version of this project.

| This Project | Python Version | Django Version |
|--------------|-----------------------------|----------------|
| 1.3.* | 3.9, 3.10, 3.11, 3.12, 3.13 | 4.2, 5.0, 5.1 |
| 1.2.* | 3.8, 3.9, 3.10, 3.11 | 3.2, 4.1, 4.2 |
| 1.1.* | 3.8, 3.9, 3.10, 3.11 | 3.2, 4.1, 4.2 |
| 1.0.* | 3.8, 3.9, 3.10, 3.11 | 3.2, 4.0, 4.1 |
| This Project | Python Version | Django Version |
|--------------|------------------------------|----------------|
| 1.4.* | 3.10, 3.11, 3.12, 3.13, 3.14 | 4.2, 5.1, 5.2 |
| 1.3.* | 3.9, 3.10, 3.11, 3.12, 3.13 | 4.2, 5.0, 5.1 |
| 1.2.* | 3.8, 3.9, 3.10, 3.11 | 3.2, 4.1, 4.2 |
| 1.1.* | 3.8, 3.9, 3.10, 3.11 | 3.2, 4.1, 4.2 |
| 1.0.* | 3.8, 3.9, 3.10, 3.11 | 3.2, 4.0, 4.1 |
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ def handle_tick(self):
for dt in relevant_dts:
if (
p_task.max_number_of_executions is not None
and self.number_of_corresponding_single_tasks(p_task)
>= p_task.max_number_of_executions
and self.number_of_corresponding_single_tasks(p_task) >= p_task.max_number_of_executions
) or (p_task.end_time is not None and p_task.end_time < dt):
p_task.is_active = False
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ def handle_tick(self):
except Exception as exc:
task.status = FutureTask.FUTURE_TASK_STATUS_ERROR
task.result = {
"exception": "An exception of type {} occurred.".format(
type(exc).__name__,
),
"exception": f"An exception of type {type(exc).__name__} occurred.",
"args": self._convert_exception_args(exc.args),
"traceback": traceback.format_exception(
*sys.exc_info(),
Expand Down
10 changes: 10 additions & 0 deletions django_future_tasks/migrations/0006_periodicfuturetask_end_time.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by Django 3.2.22 on 2023-10-20 15:42

import django
from django.db import migrations, models


Expand Down Expand Up @@ -27,6 +28,15 @@ class Migration(migrations.Migration):
_connector="OR",
),
name="not_both_not_null",
)
if django.VERSION < (5, 1)
Comment on lines +31 to +32
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

condition does not exist in 4.2 and check is deprecated in 5.1. 🙃
Changing the name seems to be not a change in the database - makemigrations says no changes.

else models.CheckConstraint(
condition=models.Q(
("end_time__isnull", True),
("max_number_of_executions__isnull", True),
_connector="OR",
),
name="not_both_not_null",
),
),
]
24 changes: 12 additions & 12 deletions django_future_tasks/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import datetime

import croniter
import django
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import JSONField, Q
from django.utils import timezone
from django.utils.dateformat import format
from django.utils import dateformat, timezone
from django.utils.translation import gettext_lazy as _

from .fields import FutureTaskCronField
Expand Down Expand Up @@ -106,18 +104,16 @@ def next_planned_execution(self):
not self.is_active
or (
self.max_number_of_executions is not None
and FutureTask.objects.filter(periodic_parent_task=self.pk).count()
>= self.max_number_of_executions
and FutureTask.objects.filter(periodic_parent_task=self.pk).count() >= self.max_number_of_executions
)
or (
self.end_time is not None
and self.end_time
< croniter.croniter(self.cron_string, now).get_next(timezone.datetime)
and self.end_time < croniter.croniter(self.cron_string, now).get_next(timezone.datetime)
)
):
return None

return format(
return dateformat.format(
timezone.template_localtime(next_planned_execution),
settings.DATETIME_FORMAT,
)
Expand All @@ -134,7 +130,7 @@ def save(
update_fields=None,
):
if self.is_active and not self.__original_is_active:
self.last_task_creation = datetime.datetime.now()
self.last_task_creation = timezone.now()
Comment on lines -137 to +133
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Might be the cause of the duplicated tasks that you encountered @nezhar .


self.clean()
super().save()
Expand All @@ -159,8 +155,12 @@ def clean(self):
class Meta:
constraints = [
models.CheckConstraint(
check=Q(end_time__isnull=True)
| Q(max_number_of_executions__isnull=True),
check=Q(end_time__isnull=True) | Q(max_number_of_executions__isnull=True),
name="not_both_not_null",
)
if django.VERSION < (5, 1)
else models.CheckConstraint(
condition=Q(end_time__isnull=True) | Q(max_number_of_executions__isnull=True),
name="not_both_not_null",
),
]
71 changes: 71 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
[project]
name = "django-future-tasks"
description = "A library to create periodic, cron-like tasks or single tasks with a specified execution/start time and schedule it to run in the future."
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "Armin Ster", email = "aster@anexia-it.com"},
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.1",
"Framework :: Django :: 5.2",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dynamic = ["version"]
dependencies = [
"croniter>=3.0.3,<6.0",
"django-cronfield>=0.2.0,<0.3",
]

[project.optional-dependencies]
dev = [
"django>=4.2,<6.0",
"pre-commit>=4.3,<4.4",
"pytest>=8.4,<8.5",
"pytest-cov>=7.0,<7.1",
"pytest-django>=4.11,<4.12",
"time-machine>=2.19.0,<2.20.0",
]

[project.urls]
Homepage = "https://github.com/anexia/django-future-tasks"
Documentation = "https://github.com/anexia/django-future-tasks/blob/main/README.md"
Repository = "https://github.com/anexia/django-future-tasks"
Issues = "https://github.com/anexia/django-future-tasks/issues"
Changelog = "https://github.com/anexia/django-future-tasks/blob/main/CHANGELOG.md"

[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "core.settings"
filterwarnings = ["error", "ignore::ResourceWarning"]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's a ResourceWarning that sometimes pop up in the tests because of an unclosed connection to SQLite that I don't really care about.


[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[tool.ruff]
line-length = 120
respect-gitignore = true
extend-exclude = [".venv", "__pycache__"]
show-fixes = true

[tool.ruff.format]
quote-style = "double"

[tool.ruff.lint]
select = ["A", "B", "C", "E", "F", "W", "T20", "LOG", "I", "UP", "RUF010", "RUF019"]
ignore = ["E203", "E266", "E501", "F403", "F405"]
Comment on lines +66 to +68
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I threw our usual config in there after noticing that imports were not automatically sorted and fixed all the issues that popped up because of the stricter config.


[tool.ruff.lint.isort]
known-first-party = ["django_future_tasks", "core"]
16 changes: 0 additions & 16 deletions requirements.txt

This file was deleted.

6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

setup(
name="django-future-tasks",
version=os.getenv("PACKAGE_VERSION", "1.3.0").replace("refs/tags/", ""),
version=os.getenv("PACKAGE_VERSION", "1.3.2").replace("refs/tags/", ""),
packages=find_packages(),
include_package_data=True,
install_requires=[
Expand All @@ -28,17 +28,17 @@
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Framework :: Django :: 5.2",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
],
)
Loading