From 2c196e00a2f9b985ec96c2a050a89f37cec2546c Mon Sep 17 00:00:00 2001 From: GusCayresMindsight Date: Thu, 7 Aug 2025 15:55:03 +0000 Subject: [PATCH] feat: drop support for Python <3.7 and add support for Python 3.12 --- .../management/commands/process_tasks.py | 5 +- background_task/models.py | 6 +- background_task/models_completed.py | 4 +- background_task/signals.py | 10 +- background_task/tasks.py | 12 +-- background_task/tests/test_tasks.py | 96 +++++++++---------- requirements.txt | 3 +- tox.ini | 7 +- 8 files changed, 68 insertions(+), 75 deletions(-) diff --git a/background_task/management/commands/process_tasks.py b/background_task/management/commands/process_tasks.py index c349388..02068b1 100644 --- a/background_task/management/commands/process_tasks.py +++ b/background_task/management/commands/process_tasks.py @@ -9,7 +9,10 @@ from background_task.tasks import tasks, autodiscover from background_task.utils import SignalManager -from compat import close_connection +from django.db import connection + +def close_connection(): + connection.close() logger = logging.getLogger(__name__) diff --git a/background_task/models.py b/background_task/models.py index 714e838..06d6d63 100644 --- a/background_task/models.py +++ b/background_task/models.py @@ -6,13 +6,12 @@ import os import traceback -from compat import StringIO -from compat.models import GenericForeignKey +from io import StringIO +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Q from django.utils import timezone -from django.utils.six import python_2_unicode_compatible from background_task.exceptions import InvalidTaskError from background_task.settings import app_settings @@ -128,7 +127,6 @@ def drop_task(self, task_name, args=None, kwargs=None): return self.get_task(task_name, args, kwargs).delete() -@python_2_unicode_compatible class Task(models.Model): # the "name" of the task/function to be run task_name = models.CharField(max_length=190, db_index=True) diff --git a/background_task/models_completed.py b/background_task/models_completed.py index c7739a7..c637d52 100644 --- a/background_task/models_completed.py +++ b/background_task/models_completed.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- import os -from compat.models import GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils import timezone -from django.utils.six import python_2_unicode_compatible from background_task.models import Task @@ -51,7 +50,6 @@ def succeeded(self, within=None): return qs -@python_2_unicode_compatible class CompletedTask(models.Model): # the "name" of the task/function to be run task_name = models.CharField(max_length=190, db_index=True) diff --git a/background_task/signals.py b/background_task/signals.py index f927d7f..3ec7acc 100644 --- a/background_task/signals.py +++ b/background_task/signals.py @@ -3,11 +3,11 @@ from django.db import connections from background_task.settings import app_settings -task_created = django.dispatch.Signal(providing_args=['task']) -task_error = django.dispatch.Signal(providing_args=['task']) -task_rescheduled = django.dispatch.Signal(providing_args=['task']) -task_failed = django.dispatch.Signal(providing_args=['task_id', 'completed_task']) -task_successful = django.dispatch.Signal(providing_args=['task_id', 'completed_task']) +task_created = django.dispatch.Signal() +task_error = django.dispatch.Signal() +task_rescheduled = django.dispatch.Signal() +task_failed = django.dispatch.Signal() +task_successful = django.dispatch.Signal() task_started = django.dispatch.Signal() task_finished = django.dispatch.Signal() diff --git a/background_task/tasks.py b/background_task/tasks.py index eaaf987..3642b24 100644 --- a/background_task/tasks.py +++ b/background_task/tasks.py @@ -10,7 +10,6 @@ from django.db.utils import OperationalError from django.utils import timezone -from django.utils.six import python_2_unicode_compatible from background_task.exceptions import BackgroundTaskError from background_task.models import Task @@ -266,7 +265,6 @@ def run_next_task(self, tasks, queue=None): return False -@python_2_unicode_compatible class TaskProxy(object): def __init__(self, name, task_function, schedule, queue, remove_existing_tasks, runner): self.name = name @@ -305,7 +303,7 @@ def autodiscover(): """ Autodiscover tasks.py files in much the same way as admin app """ - import imp + import importlib from django.conf import settings for app in settings.INSTALLED_APPS: @@ -313,9 +311,9 @@ def autodiscover(): app_path = import_module(app).__path__ except (AttributeError, ImportError): continue - try: - imp.find_module('tasks', app_path) - except ImportError: + + # Check if tasks module exists + if importlib.util.find_spec(f"{app}.tasks") is None: continue - import_module("%s.tasks" % app) + import_module(f"{app}.tasks") diff --git a/background_task/tests/test_tasks.py b/background_task/tests/test_tasks.py index 2412672..7012273 100644 --- a/background_task/tests/test_tasks.py +++ b/background_task/tests/test_tasks.py @@ -108,7 +108,7 @@ def test_str(self): def test_shortcut(self): '''check shortcut to decorator works''' proxy = background()(empty_task) - self.failIfEqual(proxy, empty_task) + self.assertNotEqual(proxy, empty_task) self.assertEqual(proxy.task_function, empty_task) def test_launch_sync(self): @@ -151,9 +151,9 @@ def test_priority(self): self.assertEqual(2, TaskSchedule(priority=2).priority) def _within_one_second(self, d1, d2): - self.failUnless(isinstance(d1, datetime)) - self.failUnless(isinstance(d2, datetime)) - self.failUnless(abs(d1 - d2) <= timedelta(seconds=1)) + self.assertTrue(isinstance(d1, datetime)) + self.assertTrue(isinstance(d2, datetime)) + self.assertTrue(abs(d1 - d2) <= timedelta(seconds=1)) def test_run_at(self): for schedule in [None, 0, timedelta(seconds=0)]: @@ -268,8 +268,8 @@ def reschedule_fn(): # check task is scheduled for later on now = timezone.now() - self.failUnless(now + timedelta(seconds=89) < task.run_at) - self.failUnless(now + timedelta(seconds=91) > task.run_at) + self.assertTrue(now + timedelta(seconds=89) < task.run_at) + self.assertTrue(now + timedelta(seconds=91) > task.run_at) def test_check_existing(self): @@ -292,8 +292,8 @@ def check_fn(): # check new task is scheduled for the earlier time now = timezone.now() - self.failUnless(now - timedelta(seconds=1) < task.run_at) - self.failUnless(now + timedelta(seconds=1) > task.run_at) + self.assertTrue(now - timedelta(seconds=1) < task.run_at) + self.assertTrue(now + timedelta(seconds=1) > task.run_at) class TestTaskRunner(TransactionTestCase): @@ -303,19 +303,19 @@ def setUp(self): self.runner = tasks._runner def test_get_task_to_run_no_tasks(self): - self.failIf(self.runner.get_task_to_run(tasks)) + self.assertFalse(self.runner.get_task_to_run(tasks)) def test_get_task_to_run(self): task = Task.objects.new_task('mytask', (1), {}) task.save() - self.failUnless(task.locked_by is None) - self.failUnless(task.locked_at is None) + self.assertTrue(task.locked_by is None) + self.assertTrue(task.locked_at is None) locked_task = self.runner.get_task_to_run(tasks) - self.failIf(locked_task is None) - self.failIf(locked_task.locked_by is None) + self.assertFalse(locked_task is None) + self.assertFalse(locked_task.locked_by is None) self.assertEqual(self.runner.worker_name, locked_task.locked_by) - self.failIf(locked_task.locked_at is None) + self.assertFalse(locked_task.locked_at is None) self.assertEqual('mytask', locked_task.task_name) @@ -324,12 +324,12 @@ class TestTaskModel(TransactionTestCase): def test_lock_uncontested(self): task = Task.objects.new_task('mytask') task.save() - self.failUnless(task.locked_by is None) - self.failUnless(task.locked_at is None) + self.assertTrue(task.locked_by is None) + self.assertTrue(task.locked_at is None) locked_task = task.lock('mylock') self.assertEqual('mylock', locked_task.locked_by) - self.failIf(locked_task.locked_at is None) + self.assertFalse(locked_task.locked_at is None) self.assertEqual(task.pk, locked_task.pk) def test_lock_contested(self): @@ -337,9 +337,9 @@ def test_lock_contested(self): # in memory task = Task.objects.new_task('mytask') task.save() - self.failIf(task.lock('mylock') is None) + self.assertFalse(task.lock('mylock') is None) - self.failUnless(task.lock('otherlock') is None) + self.assertTrue(task.lock('otherlock') is None) def test_lock_expired(self): task = Task.objects.new_task('mytask') @@ -352,7 +352,7 @@ def test_lock_expired(self): locked_task.save() # now try to get the lock again - self.failIf(task.lock('otherlock') is None) + self.assertFalse(task.lock('otherlock') is None) def test_str(self): task = Task.objects.new_task('mytask') @@ -413,16 +413,16 @@ def throws_error(): self.throws_error = throws_error def test_run_next_task_nothing_scheduled(self): - self.failIf(run_next_task()) + self.assertFalse(run_next_task()) def test_run_next_task_one_task_scheduled(self): self.set_fields(worked=True) - self.failIf(hasattr(self, 'worked')) + self.assertFalse(hasattr(self, 'worked')) - self.failUnless(run_next_task()) + self.assertTrue(run_next_task()) - self.failUnless(hasattr(self, 'worked')) - self.failUnless(self.worked) + self.assertTrue(hasattr(self, 'worked')) + self.assertTrue(self.worked) def test_run_next_task_several_tasks_scheduled(self): self.set_fields(one='1') @@ -430,12 +430,12 @@ def test_run_next_task_several_tasks_scheduled(self): self.set_fields(three='3') for i in range(3): - self.failUnless(run_next_task()) + self.assertTrue(run_next_task()) - self.failIf(run_next_task()) # everything should have been run + self.assertFalse(run_next_task()) # everything should have been run for field, value in [('one', '1'), ('two', '2'), ('three', '3')]: - self.failUnless(hasattr(self, field)) + self.assertTrue(hasattr(self, field)) self.assertEqual(value, getattr(self, field)) def test_run_next_task_error_handling(self): @@ -446,35 +446,35 @@ def test_run_next_task_error_handling(self): original_task = all_tasks[0] # should run, but trigger error - self.failUnless(run_next_task()) + self.assertTrue(run_next_task()) all_tasks = Task.objects.all() self.assertEqual(1, all_tasks.count()) failed_task = all_tasks[0] # should have an error recorded - self.failIfEqual('', failed_task.last_error) - self.failUnless(failed_task.failed_at is None) + self.assertNotEqual('', failed_task.last_error) + self.assertTrue(failed_task.failed_at is None) self.assertEqual(1, failed_task.attempts) # should have been rescheduled for the future # and no longer locked - self.failUnless(failed_task.run_at > original_task.run_at) - self.failUnless(failed_task.locked_by is None) - self.failUnless(failed_task.locked_at is None) + self.assertTrue(failed_task.run_at > original_task.run_at) + self.assertTrue(failed_task.locked_by is None) + self.assertTrue(failed_task.locked_at is None) def test_run_next_task_does_not_run_locked(self): self.set_fields(locked=True) - self.failIf(hasattr(self, 'locked')) + self.assertFalse(hasattr(self, 'locked')) all_tasks = Task.objects.all() self.assertEqual(1, all_tasks.count()) original_task = all_tasks[0] original_task.lock('lockname') - self.failIf(run_next_task()) + self.assertFalse(run_next_task()) - self.failIf(hasattr(self, 'locked')) + self.assertFalse(hasattr(self, 'locked')) all_tasks = Task.objects.all() self.assertEqual(1, all_tasks.count()) @@ -486,9 +486,9 @@ def test_run_next_task_unlocks_after_MAX_RUN_TIME(self): original_task = all_tasks[0] locked_task = original_task.lock('lockname') - self.failIf(run_next_task()) + self.assertFalse(run_next_task()) - self.failIf(hasattr(self, 'lock_overridden')) + self.assertFalse(hasattr(self, 'lock_overridden')) # put lot time into past expire_by = timedelta(seconds=(app_settings.BACKGROUND_TASK_MAX_RUN_TIME + 2)) @@ -497,11 +497,11 @@ def test_run_next_task_unlocks_after_MAX_RUN_TIME(self): # so now we should be able to override the lock # and run the task - self.failUnless(run_next_task()) + self.assertTrue(run_next_task()) self.assertEqual(0, Task.objects.count()) - self.failUnless(hasattr(self, 'lock_overridden')) - self.failUnless(self.lock_overridden) + self.assertTrue(hasattr(self, 'lock_overridden')) + self.assertTrue(self.lock_overridden) def test_default_schedule_used_for_run_at(self): @@ -516,9 +516,9 @@ def default_schedule_used_for_time(): self.assertEqual(1, all_tasks.count()) task = all_tasks[0] - self.failUnless(now < task.run_at) - self.failUnless((task.run_at - now) <= timedelta(seconds=61)) - self.failUnless((task.run_at - now) >= timedelta(seconds=59)) + self.assertTrue(now < task.run_at) + self.assertTrue((task.run_at - now) <= timedelta(seconds=61)) + self.assertTrue((task.run_at - now) >= timedelta(seconds=59)) def test_default_schedule_used_for_priority(self): @@ -561,14 +561,14 @@ def failed_at_set_after_MAX_ATTEMPTS(): self.assertEqual(1, available.count()) task = available[0] - self.failUnless(task.failed_at is None) + self.assertTrue(task.failed_at is None) task.attempts = app_settings.BACKGROUND_TASK_MAX_ATTEMPTS task.save() # task should be scheduled to run now # but will be marked as failed straight away - self.failUnless(run_next_task()) + self.assertTrue(run_next_task()) available = Task.objects.find_available() self.assertEqual(0, available.count()) @@ -577,7 +577,7 @@ def failed_at_set_after_MAX_ATTEMPTS(): self.assertEqual(0, all_tasks.count()) self.assertEqual(1, CompletedTask.objects.count()) completed_task = CompletedTask.objects.all()[0] - self.failIf(completed_task.failed_at is None) + self.assertFalse(completed_task.failed_at is None) def test_run_task_return_value(self): return_value = self.set_fields(test='test') diff --git a/requirements.txt b/requirements.txt index f7de820..8b13789 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -django-compat>=1.0.13 -six + diff --git a/tox.ini b/tox.ini index b691d47..77e9964 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,7 @@ [tox] envlist = - py{27}-django{18,111}-{sync,async} - py{34}-django{18,111,20}-{sync,async} - py{35}-django{18,111,20,21}-{sync,async} - py{36}-django{111,20,21}-{sync,async} py{37}-django{20,21}-{sync,async} - + py{312}-django{52}-{sync,async} [testenv] deps = coverage @@ -14,6 +10,7 @@ deps = django111: Django>=1.11,<1.12 django20: Django>=2.0,<2.1 django21: Django>=2.1,<2.2 + django52: Django>=5.2,<6 -r{toxinidir}/requirements-test.txt -r{toxinidir}/requirements.txt