steady-queue 0.1.6__tar.gz → 0.1.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {steady_queue-0.1.6/steady_queue.egg-info → steady_queue-0.1.8}/PKG-INFO +2 -3
  2. {steady_queue-0.1.6 → steady_queue-0.1.8}/README.md +1 -2
  3. {steady_queue-0.1.6 → steady_queue-0.1.8}/pyproject.toml +3 -0
  4. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/__init__.py +2 -2
  5. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/configuration.py +3 -0
  6. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/maintenance.py +2 -1
  7. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/pidfile.py +5 -0
  8. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/registrable.py +2 -0
  9. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/supervisor.py +2 -2
  10. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/timer.py +2 -2
  11. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/recurring_task.py +1 -1
  12. {steady_queue-0.1.6 → steady_queue-0.1.8/steady_queue.egg-info}/PKG-INFO +2 -3
  13. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/SOURCES.txt +1 -0
  14. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_configuration.py +10 -0
  15. steady_queue-0.1.8/tests/test_pidfile.py +42 -0
  16. {steady_queue-0.1.6 → steady_queue-0.1.8}/LICENSE +0 -0
  17. {steady_queue-0.1.6 → steady_queue-0.1.8}/setup.cfg +0 -0
  18. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/admin.py +0 -0
  19. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/app_executor.py +0 -0
  20. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/apps.py +0 -0
  21. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/arguments.py +0 -0
  22. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/backend.py +0 -0
  23. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/collections.py +0 -0
  24. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/concurrency.py +0 -0
  25. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/db_router.py +0 -0
  26. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/management/__init__.py +0 -0
  27. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/management/commands/__init__.py +0 -0
  28. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/management/commands/steady_queue.py +0 -0
  29. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/migrations/0001_initial.py +0 -0
  30. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/migrations/__init__.py +0 -0
  31. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/__init__.py +0 -0
  32. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/base.py +0 -0
  33. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/blocked_execution.py +0 -0
  34. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/claimed_execution.py +0 -0
  35. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/clearable.py +0 -0
  36. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/concurrency_controls.py +0 -0
  37. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/dispatching.py +0 -0
  38. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/executable.py +0 -0
  39. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/execution.py +0 -0
  40. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/executor.py +0 -0
  41. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/failed_execution.py +0 -0
  42. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/job.py +0 -0
  43. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/pause.py +0 -0
  44. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/process.py +0 -0
  45. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/prunable.py +0 -0
  46. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/queue.py +0 -0
  47. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/ready_execution.py +0 -0
  48. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/recurring_execution.py +0 -0
  49. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/recurring_task.py +0 -0
  50. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/retryable.py +0 -0
  51. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/schedulable.py +0 -0
  52. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/scheduled_execution.py +0 -0
  53. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/models/semaphore.py +0 -0
  54. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/__init__.py +0 -0
  55. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/base.py +0 -0
  56. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/concurrent.py +0 -0
  57. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/dispatcher.py +0 -0
  58. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/errors.py +0 -0
  59. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/interruptible.py +0 -0
  60. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/pidfiled.py +0 -0
  61. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/poller.py +0 -0
  62. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/pool.py +0 -0
  63. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/runnable.py +0 -0
  64. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/scheduler.py +0 -0
  65. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/signals.py +0 -0
  66. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/supervised.py +0 -0
  67. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/processes/worker.py +0 -0
  68. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/queue_selector.py +0 -0
  69. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/recurring_schedule.py +0 -0
  70. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue/task.py +0 -0
  71. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/dependency_links.txt +0 -0
  72. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/requires.txt +0 -0
  73. {steady_queue-0.1.6 → steady_queue-0.1.8}/steady_queue.egg-info/top_level.txt +0 -0
  74. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_arguments.py +0 -0
  75. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_backend.py +0 -0
  76. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_concurrency_controls.py +0 -0
  77. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_db_router.py +0 -0
  78. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_executions.py +0 -0
  79. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_job.py +0 -0
  80. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_process_lifecycle.py +0 -0
  81. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_queue_selector.py +0 -0
  82. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_recurring_task.py +0 -0
  83. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_semaphore.py +0 -0
  84. {steady_queue-0.1.6 → steady_queue-0.1.8}/tests/test_task.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steady-queue
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: An asynchronous task library for Django that supports delayed tasks, concurrency controls, recurring tasks, pausing queues, numeric priorities per task, priorities by queue order and bulk enqueueing.
5
5
  Author-email: Elias Hernandis <elias@hernandis.me>
6
6
  License-Expression: MIT
@@ -505,8 +505,7 @@ There are several settings that control how Steady Queue works that you can set
505
505
  requesting immediate termination—defaults to 5 seconds.
506
506
  - `supervisor_pidfile`: path to a pidfile that the supervisor will create when
507
507
  booting to prevent running more than one supervisor in the same host, or in
508
- case you want to use it for a health check. It's set to
509
- `tmp/pids/steady_queue_supervisor.pid` by default.
508
+ case you want to use it for a health check. It's `None` by default.
510
509
  - `preserve_finished_jobs`: whether to keep finished jobs in the
511
510
  `steady_queue_jobs` table—defaults to `True`.
512
511
  - `clear_finished_jobs_after`: period to keep finished jobs around, in case
@@ -479,8 +479,7 @@ There are several settings that control how Steady Queue works that you can set
479
479
  requesting immediate termination—defaults to 5 seconds.
480
480
  - `supervisor_pidfile`: path to a pidfile that the supervisor will create when
481
481
  booting to prevent running more than one supervisor in the same host, or in
482
- case you want to use it for a health check. It's set to
483
- `tmp/pids/steady_queue_supervisor.pid` by default.
482
+ case you want to use it for a health check. It's `None` by default.
484
483
  - `preserve_finished_jobs`: whether to keep finished jobs in the
485
484
  `steady_queue_jobs` table—defaults to `True`.
486
485
  - `clear_finished_jobs_after`: period to keep finished jobs around, in case
@@ -64,6 +64,9 @@ ignore_missing_imports = true
64
64
  django_settings_module = "tests.settings"
65
65
 
66
66
  [dependency-groups]
67
+ docs = [
68
+ "sphinx>=8.0",
69
+ ]
67
70
  dev = [
68
71
  "coverage>=7.13.4",
69
72
  "django-environ>=0.12.0",
@@ -1,7 +1,7 @@
1
1
  from datetime import timedelta
2
2
  from typing import Optional
3
3
 
4
- VERSION = (0, 1, 6)
4
+ VERSION = (0, 1, 8)
5
5
 
6
6
  __version__ = ".".join(map(str, VERSION))
7
7
 
@@ -17,6 +17,6 @@ clear_finished_jobs_after: timedelta = timedelta(days=1)
17
17
 
18
18
  default_concurrency_control_period: timedelta = timedelta(minutes=3)
19
19
 
20
- supervisor_pidfile: Optional[str] = "tmp/pids/steady_queue_supervisor.pid"
20
+ supervisor_pidfile: Optional[str] = None
21
21
 
22
22
  database: str = "default"
@@ -132,6 +132,9 @@ class Configuration:
132
132
  if self.options.only_work:
133
133
  return self.workers
134
134
 
135
+ if self.skip_recurring:
136
+ return self.workers + self.dispatchers
137
+
135
138
  return self.workers + self.dispatchers + self.schedulers
136
139
 
137
140
  @property
@@ -21,7 +21,8 @@ class Maintenance:
21
21
  self.maintenance_task.start()
22
22
 
23
23
  def stop_maintenance_task(self):
24
- self.maintenance_task.stop()
24
+ if hasattr(self, "maintenance_task"):
25
+ self.maintenance_task.stop()
25
26
 
26
27
  def fail_orphaned_executions(self):
27
28
  with AppExecutor.wrap_in_app_executor():
@@ -1,3 +1,4 @@
1
+ import atexit
1
2
  import os
2
3
 
3
4
 
@@ -9,6 +10,7 @@ class Pidfile:
9
10
  def setup(self):
10
11
  self.check_status()
11
12
  self.write_file()
13
+ self.set_at_exit_hook()
12
14
 
13
15
  def delete(self):
14
16
  self.delete_file()
@@ -36,6 +38,9 @@ class Pidfile:
36
38
  self.check_status()
37
39
  self.write_file()
38
40
 
41
+ def set_at_exit_hook(self):
42
+ atexit.register(lambda: self.delete() if os.getpid() == self.pid else None)
43
+
39
44
  def delete_file(self):
40
45
  try:
41
46
  os.unlink(self.path)
@@ -63,6 +63,8 @@ class Registrable(Base):
63
63
  self.heartbeat_task.start()
64
64
 
65
65
  def stop_heartbeat(self):
66
+ if not hasattr(self, "heartbeat_task"):
67
+ return
66
68
  logger.debug("stopping heartbeat for %s", self.name)
67
69
  self.heartbeat_task.stop()
68
70
  logger.debug("stopped heartbeat for %s", self.name)
@@ -48,7 +48,7 @@ class Supervisor(Maintenance, Signals, Pidfiled, Registrable, Interruptible, Bas
48
48
  except SystemExit:
49
49
  logger.info("supervisor interrupted during boot, shutting down")
50
50
  self.restore_default_signal_handlers()
51
- self.delete_pidfile()
51
+ self.shutdown()
52
52
  return
53
53
  self.supervise()
54
54
 
@@ -154,7 +154,7 @@ class Supervisor(Maintenance, Signals, Pidfiled, Registrable, Interruptible, Bas
154
154
  if terminated_fork and (not is_exited or exit_status > 0):
155
155
  self.handle_claimed_jobs_by(terminated_fork, wait_status)
156
156
 
157
- self.configured_processes.pop(pid)
157
+ self.configured_processes.pop(pid, None)
158
158
 
159
159
  def replace_fork(self, pid: int, exitcode: int) -> None:
160
160
  logger.info("replacing fork %s due to exit code %s", pid, exitcode)
@@ -39,7 +39,7 @@ class TimerTask:
39
39
  self._stop_event = threading.Event()
40
40
 
41
41
  def start(self):
42
- self.thread = threading.Thread(target=self.run)
42
+ self.thread = threading.Thread(target=self.run, daemon=True)
43
43
  self.thread.start()
44
44
 
45
45
  def stop(self):
@@ -69,7 +69,7 @@ class TimerTask:
69
69
 
70
70
  def perform_task(self):
71
71
  # Run the callable in a separate thread to isolate crashes
72
- work_thread = threading.Thread(target=self.wrapped_callable)
72
+ work_thread = threading.Thread(target=self.wrapped_callable, daemon=True)
73
73
  work_thread.start()
74
74
  work_thread.join()
75
75
 
@@ -20,7 +20,7 @@ def recurring(
20
20
  """
21
21
  Decorator for registering a task to run on a recurring schedule.
22
22
 
23
- Usage:
23
+ Example::
24
24
 
25
25
  @recurring(schedule="*/1 * * * *", key="unique_task_key")
26
26
  @task()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steady-queue
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: An asynchronous task library for Django that supports delayed tasks, concurrency controls, recurring tasks, pausing queues, numeric priorities per task, priorities by queue order and bulk enqueueing.
5
5
  Author-email: Elias Hernandis <elias@hernandis.me>
6
6
  License-Expression: MIT
@@ -505,8 +505,7 @@ There are several settings that control how Steady Queue works that you can set
505
505
  requesting immediate termination—defaults to 5 seconds.
506
506
  - `supervisor_pidfile`: path to a pidfile that the supervisor will create when
507
507
  booting to prevent running more than one supervisor in the same host, or in
508
- case you want to use it for a health check. It's set to
509
- `tmp/pids/steady_queue_supervisor.pid` by default.
508
+ case you want to use it for a health check. It's `None` by default.
510
509
  - `preserve_finished_jobs`: whether to keep finished jobs in the
511
510
  `steady_queue_jobs` table—defaults to `True`.
512
511
  - `clear_finished_jobs_after`: period to keep finished jobs around, in case
@@ -74,6 +74,7 @@ tests/test_configuration.py
74
74
  tests/test_db_router.py
75
75
  tests/test_executions.py
76
76
  tests/test_job.py
77
+ tests/test_pidfile.py
77
78
  tests/test_process_lifecycle.py
78
79
  tests/test_queue_selector.py
79
80
  tests/test_recurring_task.py
@@ -220,6 +220,16 @@ class ConfigurationValidationTestCase(SimpleTestCase):
220
220
  errors = config.validate_recurring_tasks()
221
221
  self.assertEqual(len(errors), 0)
222
222
 
223
+ def test_skip_recurring_excludes_schedulers_from_configured_processes(self):
224
+ """skip_recurring option excludes scheduler processes."""
225
+ options = Configuration.Options(skip_recurring=True)
226
+ config = Configuration(options)
227
+
228
+ process_kinds = [p.kind for p in config.configured_processes]
229
+ self.assertNotIn("scheduler", process_kinds)
230
+ self.assertIn("worker", process_kinds)
231
+ self.assertIn("dispatcher", process_kinds)
232
+
223
233
 
224
234
  class RecurringTaskConfigurationTestCase(SimpleTestCase):
225
235
  """Tests for recurring task configuration."""
@@ -0,0 +1,42 @@
1
+ import os
2
+ from tempfile import TemporaryDirectory
3
+ from unittest.mock import patch
4
+
5
+ from django.test import SimpleTestCase
6
+
7
+ import steady_queue
8
+ from steady_queue.processes.pidfile import Pidfile
9
+
10
+
11
+ class PidfileDefaultsTestCase(SimpleTestCase):
12
+ def test_supervisor_pidfile_defaults_to_none(self):
13
+ self.assertIsNone(steady_queue.supervisor_pidfile)
14
+
15
+
16
+ class PidfileLifecycleTestCase(SimpleTestCase):
17
+ def test_setup_registers_atexit_cleanup_hook(self):
18
+ with TemporaryDirectory() as temp_dir, patch("atexit.register") as register:
19
+ path = os.path.join(temp_dir, "pids", "steady_queue_supervisor.pid")
20
+
21
+ pidfile = Pidfile(path)
22
+ pidfile.setup()
23
+
24
+ register.assert_called_once()
25
+ self.assertTrue(os.path.exists(path))
26
+ with open(path) as f:
27
+ self.assertEqual(str(os.getpid()), f.read().strip())
28
+
29
+ def test_setup_replaces_stale_pidfile_for_dead_process(self):
30
+ with TemporaryDirectory() as temp_dir:
31
+ path = os.path.join(temp_dir, "pids", "steady_queue_supervisor.pid")
32
+
33
+ os.makedirs(os.path.dirname(path), exist_ok=True)
34
+ with open(path, "w") as f:
35
+ f.write("999999")
36
+
37
+ with patch("atexit.register"):
38
+ pidfile = Pidfile(path)
39
+ pidfile.setup()
40
+
41
+ with open(path) as f:
42
+ self.assertEqual(str(os.getpid()), f.read().strip())
File without changes
File without changes