Compare commits

..
Author SHA1 Message Date
Stan c34b4fb3a6 add support for 3.14 2026-08-02 03:38:45 +02:00
Stan ab8e117e7e update matrix 2026-08-01 05:17:02 +02:00
15 changed files with 72 additions and 139 deletions
+24 -38
View File
@@ -6,21 +6,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
# for code coverage comment
permissions:
contents: read
pull-requests: write
env:
MONGO_HOST: "127.0.0.1"
REDIS_HOST: "127.0.0.1"
AWS_ENDPOINT_URL: http://localhost:4566
AWS_REGION: "us-east-1"
AWS_ACCESS_KEY_ID: "test"
AWS_SECRET_ACCESS_KEY: "test"
AWS_DEFAULT_REGION: "us-east-1"
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -50,18 +35,12 @@ jobs:
django: django:
- "5.2" - "5.2"
- "6.0" - "6.0"
- "6.1"
exclude: exclude:
# django 6.0 does not support earlier than 3.12 # django 6.0 does not support earlier than 3.12
- python-version: "3.10" - python-version: "3.10"
django: "6.0" django: "6.0"
- python-version: "3.11" - python-version: "3.11"
django: "6.0" django: "6.0"
# django 6.1 does not support earlier than 3.12
- python-version: "3.10"
django: "6.1"
- python-version: "3.11"
django: "6.1"
services: services:
mongodb: mongodb:
@@ -83,13 +62,6 @@ jobs:
ports: ports:
- 6379:6379 - 6379:6379
options: --entrypoint redis-server options: --entrypoint redis-server
aws:
image: ministackorg/ministack:1.4.16
env:
SERVICES: sqs
MINISTACK_HOST: aws
ports:
- 4566:4566
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
@@ -101,16 +73,30 @@ jobs:
python -m pip install uv==0.12.1 python -m pip install uv==0.12.1
uv sync --extra testing --python ${{ matrix.python-version }} uv sync --extra testing --python ${{ matrix.python-version }}
uv pip install "django~=${{ matrix.django }}a1" uv pip install "django~=${{ matrix.django }}a1"
- name: Create SQS queue
run: aws sqs create-queue --queue-name testing
- name: Run Tests - name: Run Tests
run: | run: |
uv run pytest --cov=./django_q --cov-report=xml --junitxml=pytest.xml --cov-report=term-missing:skip-covered | tee pytest-coverage.txt uv run pytest --cov=./django_q --cov-report=xml
env:
MONGO_HOST: "127.0.0.1"
REDIS_HOST: "127.0.0.1"
- name: Upload to coveralls
run: |
python -m pip install coveralls
coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_FLAG_NAME: python-${{ matrix.python-version }}-django-${{ matrix.django }}
COVERALLS_PARALLEL: true
- name: Coverage comment finish:
if: matrix.python-version == '3.13' && matrix.django == '5.2' && github.event_name == 'pull_request' needs: test
uses: MishaKav/pytest-coverage-comment@v1 runs-on: ubuntu-latest
with: container: python:3.11-bookworm
pytest-coverage-path: ./pytest-coverage.txt steps:
junitxml-path: ./pytest.xml - name: Upload to coveralls
report-only-changed-files: true run: |
python -m pip install --upgrade pip
python -m pip install coveralls
coveralls --service=github --finish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-16
View File
@@ -1,21 +1,5 @@
# Changelog # Changelog
## [v1.11.1](https://github.com/django-q2/django-q2/tree/v1.11.1) (2026-08-26)
- Swap localstorage to ministack (#343) https://github.com/django-q2/django-q2/pull/343
- Show test coverage in github pull request comment (#344) https://github.com/django-q2/django-q2/pull/344
- Fix cluster requesting hardcoded unix-only fork context (#347) https://github.com/django-q2/django-q2/pull/347
## [v1.11.0](https://github.com/django-q2/django-q2/tree/v1.11.0) (2026-08-10)
- AttributeError when start_event is None, and guard process faster stop (#305) https://github.com/django-q2/django-q2/pull/305
- Add croniter as optional extra. (#336) https://github.com/django-q2/django-q2/pull/336
- Migrate poetry to uv (#337) https://github.com/django-q2/django-q2/pull/337
- Add support for python 3.13 and 3.14 (#338) https://github.com/django-q2/django-q2/pull/338
- Add django 6.1 support (#340) https://github.com/django-q2/django-q2/pull/340
- Postpone SECRET_KEY evaluation to the qcluster command execution (#332) https://github.com/django-q2/django-q2/pull/332
## [v1.10.0](https://github.com/django-q2/django-q2/tree/v1.10.0) (2026-05-01) ## [v1.10.0](https://github.com/django-q2/django-q2/tree/v1.10.0) (2026-05-01)
- fix: Fix incorrect signal import (#308) https://github.com/django-q2/django-q2/pull/308 - fix: Fix incorrect signal import (#308) https://github.com/django-q2/django-q2/pull/308
+2 -2
View File
@@ -35,13 +35,13 @@ See the `changelog <https://github.com/GDay/django-q2/blob/master/CHANGELOG.md>`
Requirements Requirements
~~~~~~~~~~~~ ~~~~~~~~~~~~
- `Django <https://www.djangoproject.com>`__ 5.2, 6.0 and 6.1 - `Django <https://www.djangoproject.com>`__ 5.2 and 6.0
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__ - `Django-picklefield <https://github.com/gintas/django-picklefield>`__
Tested with: Tested with:
* Python 3.10 to 3.14. * Python 3.10 to 3.14.
* Django 5.2, 6.0 and 6.1. * Django 5.2 and 6.0.
Brokers Brokers
~~~~~~~ ~~~~~~~
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# Note that this file needs to have the executable bit set for it to work with later localstack implementations.
export DEFAULT_REGION=us-west-2
create_sqs() {
QUEUE_NAME="$1"
TIMEOUT=${2:-60}
DL_QUEUE_URL=$(awslocal sqs create-queue --queue-name "dl-$QUEUE_NAME" --query QueueUrl --output text)
echo ">>> Created $DL_QUEUE_URL queue!"
DL_QUEUE_ARN=$(awslocal sqs get-queue-attributes --queue-url "$DL_QUEUE_URL" --attribute-names QueueArn --query Attributes.QueueArn --output text)
awslocal sqs create-queue --queue-name "$QUEUE_NAME" --attributes '{
"RedrivePolicy": "{\"deadLetterTargetArn\": \"'"$DL_QUEUE_ARN"'\",\"maxReceiveCount\":\"3\"}",
"VisibilityTimeout": "'"$TIMEOUT"'"
}'
}
# Create SQS queues
create_sqs testing
@@ -1 +0,0 @@
aws sqs create-queue --queue-name testing
+1 -1
View File
@@ -1,3 +1,3 @@
VERSION = (1, 11, 1) VERSION = (1, 10, 0)
__all__ = ["conf", "cluster", "models", "tasks"] __all__ = ["conf", "cluster", "models", "tasks"]
+1 -4
View File
@@ -41,10 +41,7 @@ from django_q.worker import worker
def get_mp_context(): def get_mp_context():
if "fork" in multiprocessing.get_all_start_methods(): return multiprocessing.get_context("fork")
return multiprocessing.get_context("fork")
else:
return multiprocessing.get_context()
class Cluster: class Cluster:
+1 -7
View File
@@ -7,7 +7,6 @@ from multiprocessing import cpu_count
from warnings import warn from warnings import warn
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_q.queues import Queue from django_q.queues import Queue
@@ -212,12 +211,7 @@ class Conf:
# Use the secret key for package signing # Use the secret key for package signing
# Django itself should raise an error if it's not configured # Django itself should raise an error if it's not configured
# but suppress the exception early to allow other parts of the app to still run. SECRET_KEY = settings.SECRET_KEY
# For example any "python manage.py ..." command still runs even if SECRET_KEY is not set.
try:
SECRET_KEY = settings.SECRET_KEY
except ImproperlyConfigured:
SECRET_KEY = None
# The redis stats key # The redis stats key
Q_STAT = f"django_q:{PREFIX}:cluster" Q_STAT = f"django_q:{PREFIX}:cluster"
-3
View File
@@ -1,6 +1,5 @@
import os import os
from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@@ -29,8 +28,6 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): def handle(self, *args, **options):
# Ensure that the cluster starts only if the SECRET_KEY is set, as it is required for signing.
settings.SECRET_KEY
# Set alternative cluster_name before creating the cluster (cluster_name is broker's queue_name, too) # Set alternative cluster_name before creating the cluster (cluster_name is broker's queue_name, too)
cluster_name = options.get("cluster_name") cluster_name = options.get("cluster_name")
if cluster_name: if cluster_name:
+6 -3
View File
@@ -172,6 +172,9 @@ def test_ironmq(monkeypatch):
broker.delete_queue() broker.delete_queue()
@pytest.mark.skipif(
not os.getenv("AWS_ACCESS_KEY_ID"), reason="requires AWS credentials"
)
def test_sqs(monkeypatch): def test_sqs(monkeypatch):
monkeypatch.setattr( monkeypatch.setattr(
Conf, Conf,
@@ -185,7 +188,6 @@ def test_sqs(monkeypatch):
) )
# check broker # check broker
broker = get_broker(list_key="testing") broker = get_broker(list_key="testing")
broker.purge_queue()
assert "receive_message_wait_time_seconds" in Conf.SQS assert "receive_message_wait_time_seconds" in Conf.SQS
assert "aws_region" in Conf.SQS assert "aws_region" in Conf.SQS
assert broker.ping() is True assert broker.ping() is True
@@ -222,9 +224,8 @@ def test_sqs(monkeypatch):
assert broker.dequeue() is None assert broker.dequeue() is None
# fail # fail
broker.enqueue("test") broker.enqueue("test")
task = None
while task is None: while task is None:
task = broker.dequeue() task = broker.dequeue()[0]
broker.fail(task[0][0]) broker.fail(task[0][0])
# bulk test # bulk test
for _ in range(10): for _ in range(10):
@@ -234,6 +235,8 @@ def test_sqs(monkeypatch):
for task in tasks: for task in tasks:
assert task is not None assert task is not None
broker.acknowledge(task[0]) broker.acknowledge(task[0])
# duplicate acknowledge
broker.acknowledge(task[0])
assert broker.lock_size() == 0 assert broker.lock_size() == 0
# delete queue # delete queue
broker.enqueue("test") broker.enqueue("test")
+1 -49
View File
@@ -1,4 +1,3 @@
import multiprocessing
import os import os
import signal import signal
import sys import sys
@@ -14,7 +13,7 @@ import pytest
from django.utils import timezone from django.utils import timezone
from django_q.brokers import Broker, get_broker from django_q.brokers import Broker, get_broker
from django_q.cluster import Cluster, Sentinel, get_mp_context from django_q.cluster import Cluster, Sentinel
from django_q.conf import Conf from django_q.conf import Conf
from django_q.humanhash import DEFAULT_WORDLIST, uuid from django_q.humanhash import DEFAULT_WORDLIST, uuid
from django_q.models import Success, Task from django_q.models import Success, Task
@@ -60,53 +59,6 @@ def broker(monkeypatch):
return get_broker() return get_broker()
def test_get_mp_context_prefers_fork_when_available(monkeypatch):
monkeypatch.setattr(
multiprocessing,
"get_all_start_methods",
lambda: ["fork", "spawn", "forkserver"],
)
calls = []
class DummyContext:
def get_start_method(self):
return "fork"
def fake_get_context(method=None):
calls.append(method)
return DummyContext()
monkeypatch.setattr(multiprocessing, "get_context", fake_get_context)
assert get_mp_context().get_start_method() == "fork"
assert calls == ["fork"]
def test_get_mp_context_falls_back_to_platform_default_without_fork(monkeypatch):
"""
Regression test: get_mp_context() used to hardcode the unix-only "fork"
context, which raises ValueError on platforms (e.g. Windows) that don't
support it. It should instead defer to the platform's default context
whenever "fork" isn't available.
"""
monkeypatch.setattr(multiprocessing, "get_all_start_methods", lambda: ["spawn"])
calls = []
real_get_context = multiprocessing.get_context
def fake_get_context(method=None):
calls.append(method)
return real_get_context(method)
monkeypatch.setattr(multiprocessing, "get_context", fake_get_context)
get_mp_context()
# Must ask for the platform default (no explicit method), should never be equal to "fork"
assert calls == [None]
def test_redis_connection(broker): def test_redis_connection(broker):
assert broker.ping() is True assert broker.ping() is True
+2 -2
View File
@@ -73,9 +73,9 @@ author = "Ilan Steemers, Stan Triepels"
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = "1.11" version = "1.10"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = "1.11.1" release = "1.10.0"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
+1 -1
View File
@@ -27,7 +27,7 @@ Features
- Rollbar and Sentry support - Rollbar and Sentry support
Django Q2 is tested with: python 3.10, 3.11, 3.12, 3.13 and 3.14. Works with Django 5.2, 6.0 and 6.1 Django Q2 is tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 4.2.x, 5.x and 6.0.x
Currently available in English, German and French. Currently available in English, German and French.
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "uv_build"
[project] [project]
name = "django-q2" name = "django-q2"
version = "1.11.1" version = "1.10.0"
packages = [ packages = [
{ include = "django_q" }, { include = "django_q" },
] ]
@@ -33,11 +33,11 @@ classifiers = [
"Operating System :: MacOS", "Operating System :: MacOS",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP",
"Topic :: System :: Distributed Computing", "Topic :: System :: Distributed Computing",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
+12 -10
View File
@@ -15,18 +15,20 @@ services:
aws: aws:
container_name: aws container_name: aws
image: ministackorg/ministack:1.4.16 image: localstack/localstack:3.4.0
ports: ports:
- "127.0.0.1:4566:4566" - "127.0.0.1:4566:4566" # LocalStack Gateway
- "127.0.0.1:4510-4559:4510-4559" # External services port range
environment: environment:
MINISTACK_HOST: aws AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-west-2}
DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-west-2}
SQS_ENDPOINT_STRATEGY: path
SERVICES: sqs SERVICES: sqs
LOCALSTACK_HOST: aws
DEBUG: 1
LS_LOG: trace
volumes: volumes:
- ./containers/ministack:/docker-entrypoint-initaws.d - ./containers/localstack:/etc/localstack/init/ready.d
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:4566/_ministack/ready"]
interval: 2s
retries: 15
networks: networks:
- main - main
@@ -36,10 +38,10 @@ services:
context: . context: .
environment: environment:
AWS_ENDPOINT_URL: http://aws:4566 AWS_ENDPOINT_URL: http://aws:4566
AWS_REGION: ${AWS_REGION:-us-east-1} AWS_REGION: ${AWS_REGION:-us-west-2}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-test} AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-test}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-test} AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-test}
AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-east-1} AWS_DEFAULT_REGION: ${AWS_DEFAULT_REGION:-us-west-2}
volumes: volumes:
- .:/app - .:/app
depends_on: depends_on: