diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1324a4d..17353b5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3884162..85aed16 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: django-version: '5.1' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 @@ -34,7 +34,7 @@ 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 @@ -42,15 +42,8 @@ jobs: 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 diff --git a/.gitignore b/.gitignore index 7b46ce0..89a5a55 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ __pycache__ db.sqlite3 build/* tests/static/* +.python-version +.coverage +uv.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ce655d..da05865 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index ae73a6c..f0baf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ 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). +## [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 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0935a47 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[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.9" +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.0", + "Framework :: Django :: 5.1", + "Intended Audience :: Developers", + "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", +] +dynamic = ["version"] +dependencies = [ + "croniter>=3.0.3,<3.1", + "django-cronfield>=0.2.0,<0.3", +] + +[project.optional-dependencies] +dev = [ + "django>=4.2,<5.2", + "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.15.0,<2.17.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" + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 927d84f..0000000 --- a/requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -# Package and package dependencies --e . - -# Development dependencies -coverage>=7.6.2,<7.7 - -# Cron -croniter>=3.0.3,<3.1 - -# TestApp dependencies -django>=4.2,<5.2 -django-cronfield>=0.2.0,<0.3 - -# Linters and formatters -pre-commit>=4.0.1,<4.1 -time-machine>=2.15.0,<2.17.0 diff --git a/setup.py b/setup.py index fbadce3..12053cd 100644 --- a/setup.py +++ b/setup.py @@ -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=[ diff --git a/tests/testapp/apps.py b/tests/testapp/apps.py index 24105e1..7e9b7dd 100644 --- a/tests/testapp/apps.py +++ b/tests/testapp/apps.py @@ -5,4 +5,4 @@ class TestappConfig(AppConfig): name = "testapp" # import signal handlers - import tests.testapp.handlers + import testapp.handlers diff --git a/tests/testapp/handlers.py b/tests/testapp/handlers.py index 926bca6..11b91c2 100644 --- a/tests/testapp/handlers.py +++ b/tests/testapp/handlers.py @@ -5,7 +5,7 @@ from django.dispatch import receiver from django_future_tasks.handlers import future_task_signal -from tests.core import settings +from core import settings @receiver(future_task_signal, sender=intern(settings.FUTURE_TASK_TYPE_ONE)) diff --git a/tests/testapp/mixins.py b/tests/testapp/mixins.py index 2f3ed51..68cd35a 100644 --- a/tests/testapp/mixins.py +++ b/tests/testapp/mixins.py @@ -20,9 +20,9 @@ def run(self): class ProcessTasksCommandMixin: @classmethod def setUpClass(cls): - assert ( - not hasattr(cls, "command_instance") or cls.command_instance is None - ), "process_future_tasks has already been started" + assert not hasattr(cls, "command_instance") or cls.command_instance is None, ( + "process_future_tasks has already been started" + ) print("Starting process_future_tasks...") cls.command_instance = ProcessTasksCommand() @@ -32,9 +32,9 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - assert ( - cls.command_instance is not None - ), "process_future_tasks has not been started and can therefore not be stopped" + assert cls.command_instance is not None, ( + "process_future_tasks has not been started and can therefore not be stopped" + ) print("Stopping process_future_tasks...") super().tearDownClass() @@ -45,9 +45,9 @@ def tearDownClass(cls): class PopulatePeriodicTaskCommandMixin: @classmethod def setUpClass(cls): - assert ( - not hasattr(cls, "command_instance") or cls.command_instance is None - ), "populate_periodic_future_tasks has already been started" + assert not hasattr(cls, "command_instance") or cls.command_instance is None, ( + "populate_periodic_future_tasks has already been started" + ) print("Starting populate_periodic_future_tasks...") cls.command_instance = PopulatePeriodicTasksCommand() @@ -57,9 +57,9 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - assert ( - cls.command_instance is not None - ), "populate_periodic_future_tasks has not been started and can therefore not be stopped" + assert cls.command_instance is not None, ( + "populate_periodic_future_tasks has not been started and can therefore not be stopped" + ) print("Stopping populate_periodic_future_tasks...") super().tearDownClass() diff --git a/tests/testapp/tests/test_future_tasks.py b/tests/testapp/tests/test_future_tasks.py index 78b3039..2a025e0 100644 --- a/tests/testapp/tests/test_future_tasks.py +++ b/tests/testapp/tests/test_future_tasks.py @@ -10,8 +10,8 @@ from django.utils import timezone from django_future_tasks.models import FutureTask -from tests.core import settings -from tests.testapp.mixins import ProcessTasksCommandMixin +from core import settings +from testapp.mixins import ProcessTasksCommandMixin class WaitForTaskStatusTimeout(Exception): diff --git a/tests/testapp/tests/test_periodic_future_tasks.py b/tests/testapp/tests/test_periodic_future_tasks.py index f2e7d51..b09ee5b 100644 --- a/tests/testapp/tests/test_periodic_future_tasks.py +++ b/tests/testapp/tests/test_periodic_future_tasks.py @@ -7,8 +7,8 @@ from django.utils import timezone from django_future_tasks.models import FutureTask, PeriodicFutureTask -from tests.core import settings -from tests.testapp.mixins import PopulatePeriodicTaskCommandMixin +from core import settings +from testapp.mixins import PopulatePeriodicTaskCommandMixin SLEEP_TIME = 1.8